The branch, v3-2-stable has been updated via 2075ce26d4f6dcb70c125e87a10d752681aa6150 (commit) via d79b519cab977df5ad78fe44b05b12da0af347f3 (commit) via fff7893c02de9781f6b57683f0515ce2532a12e8 (commit) via 137f24760861838a098ca31c8f5004efb7411e67 (commit) via 1b7af6fbfa77432aa53c46d5961135ee463c0f2b (commit) via d098105b51f05dc43f778e3ecc55abe242ec6ffe (commit) via 2ce81928df761eaa173ed164bdb672ef513f2ced (commit) from c4e59eb66d493ab41e873f8aaeb8293a188905b6 (commit)
http://gitweb.samba.org/?p=samba.git;a=shortlog;h=v3-2-stable - Log ----------------------------------------------------------------- commit 2075ce26d4f6dcb70c125e87a10d752681aa6150 Author: Günther Deschner <[EMAIL PROTECTED]> Date: Thu Apr 3 00:23:50 2008 +0200 Fix rpccli_lsa_lookup_sids_noalloc. When looking up e.g. a trusted doamin account, there is just no name. Michael, please check. Guenther (cherry picked from commit ea3cfadc2504c891b4784719bd8e6debcc38c879) commit d79b519cab977df5ad78fe44b05b12da0af347f3 Author: Günther Deschner <[EMAIL PROTECTED]> Date: Wed Apr 2 15:26:27 2008 +0200 Add NT_STATUS_RPC_CANNOT_SUPPORT. Guenther (cherry picked from commit 9e15ce03ca66a0b5ffdb39dd2faaad6e0f967e31) commit fff7893c02de9781f6b57683f0515ce2532a12e8 Author: Günther Deschner <[EMAIL PROTECTED]> Date: Wed Apr 2 12:29:24 2008 +0200 Fix "net rpc trustdom establish" for win2k8 trusts. When establishing trusts to a windows 2008 dc, the NetServerEnum2 RAP call fails with some exotic RAP failure. Let's just try a netlogon getdcname call in that case to convince ourselve we're talking to a proper machine. Rafael, looks ok? Guenther (cherry picked from commit b12edbeffee1f7d1fd971cde9189e5137ddeb35b) commit 137f24760861838a098ca31c8f5004efb7411e67 Author: Günther Deschner <[EMAIL PROTECTED]> Date: Wed Apr 2 12:23:07 2008 +0200 Apply some const in clirap. Guenther (cherry picked from commit 8a1a9f967db25d3928f19e46d60af249f934f323) commit 1b7af6fbfa77432aa53c46d5961135ee463c0f2b Author: Günther Deschner <[EMAIL PROTECTED]> Date: Wed Apr 2 11:18:10 2008 +0200 Some fixes for netdomjoin-gui and support for browsing/joining OUs. Guenther (cherry picked from commit 4714bae0dbbb2ad010c2929f83de6bca84cfac46) commit d098105b51f05dc43f778e3ecc55abe242ec6ffe Author: Günther Deschner <[EMAIL PROTECTED]> Date: Wed Apr 2 11:14:15 2008 +0200 Make sure to hand down the domain name in libnetapi NetUnjoinDomain. Guenther (cherry picked from commit 0058ab30de943f134792e3d66051206086987110) commit 2ce81928df761eaa173ed164bdb672ef513f2ced Author: Günther Deschner <[EMAIL PROTECTED]> Date: Wed Apr 2 02:29:48 2008 +0200 Fix NETLOGON credential chain with Windows 2008 all over the place. In order to avoid receiving NT_STATUS_DOWNGRADE_DETECTED from a w2k8 netr_ServerAuthenticate2 reply, we need to start with the AD netlogon negotiate flags everywhere (not only when running in security=ads). Only for NT4 we need to do a downgrade to the returned negotiate flags. Tested with w2k8, w2ksp4, w2k3r2 and nt4sp6. Guenther (cherry picked from commit 0970369ca0cb9ae465cff40e5c75739824daf1d0) ----------------------------------------------------------------------- Summary of changes: source/auth/auth_domain.c | 2 +- source/include/nterr.h | 1 + source/include/rpc_dce.h | 44 ++- .../examples/netdomjoin-gui/netdomjoin-gui.c | 547 ++++++++++++++------ source/lib/netapi/joindomain.c | 18 +- source/libnet/libnet_join.c | 3 +- source/libsmb/clirap2.c | 2 +- source/libsmb/nterr.c | 1 + source/libsmb/trusts_util.c | 2 +- source/rpc_client/cli_lsarpc.c | 14 +- source/rpc_client/cli_netlogon.c | 11 + source/rpc_client/cli_pipe.c | 4 +- source/rpcclient/rpcclient.c | 2 +- source/utils/net_rpc.c | 66 ++- source/utils/net_rpc_join.c | 4 +- source/utils/net_rpc_samsync.c | 2 +- source/winbindd/winbindd_cm.c | 6 +- 17 files changed, 528 insertions(+), 201 deletions(-) Changeset truncated at 500 lines: diff --git a/source/auth/auth_domain.c b/source/auth/auth_domain.c index c9aa064..f526677 100644 --- a/source/auth/auth_domain.c +++ b/source/auth/auth_domain.c @@ -126,7 +126,7 @@ machine %s. Error was : %s.\n", dc_name, nt_errstr(result))); if (!lp_client_schannel()) { /* We need to set up a creds chain on an unauthenticated netlogon pipe. */ - uint32 neg_flags = NETLOGON_NEG_SELECT_AUTH2_FLAGS; + uint32_t neg_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS; uint32 sec_chan_type = 0; unsigned char machine_pwd[16]; const char *account_name; diff --git a/source/include/nterr.h b/source/include/nterr.h index 5749c4e..612cf6e 100644 --- a/source/include/nterr.h +++ b/source/include/nterr.h @@ -566,5 +566,6 @@ #define NT_STATUS_DOWNGRADE_DETECTED NT_STATUS(0xC0000000 | 0x0388) #define NT_STATUS_NO_SUCH_JOB NT_STATUS(0xC0000000 | 0xEDE) /* scheduler */ #define NT_STATUS_RPC_PROTSEQ_NOT_SUPPORTED NT_STATUS(0xC0000000 | 0x20004) +#define NT_STATUS_RPC_CANNOT_SUPPORT NT_STATUS(0xC0000000 | 0x20041) #endif /* _NTERR_H */ diff --git a/source/include/rpc_dce.h b/source/include/rpc_dce.h index ec08eb5..33ab365 100644 --- a/source/include/rpc_dce.h +++ b/source/include/rpc_dce.h @@ -101,12 +101,48 @@ enum RPC_PKT_TYPE { /* The 7 here seems to be required to get Win2k not to downgrade us to NT4. Actually, anything other than 1ff would seem to do... */ #define NETLOGON_NEG_AUTH2_FLAGS 0x000701ff +/* + (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_DNS_DOMAIN_TRUSTS | + NETLOGON_NEG_PASSWORD_SET2 | + NETLOGON_NEG_GETDOMAININFO) +*/ #define NETLOGON_NEG_DOMAIN_TRUST_ACCOUNT 0x2010b000 - -/* these are the flags that ADS clients use */ -#define NETLOGON_NEG_AUTH2_ADS_FLAGS (0x200fbffb | NETLOGON_NEG_ARCFOUR | NETLOGON_NEG_128BIT | NETLOGON_NEG_SCHANNEL) -#define NETLOGON_NEG_SELECT_AUTH2_FLAGS ((lp_security() == SEC_ADS) ? NETLOGON_NEG_AUTH2_ADS_FLAGS : NETLOGON_NEG_AUTH2_FLAGS) +/* these are the flags that ADS clients use */ +#define NETLOGON_NEG_AUTH2_ADS_FLAGS 0x600fffff +/* + (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_SEND_PASSWORD_INFO_PDC | + NETLOGON_NEG_GENERIC_PASSTHROUGH | + NETLOGON_NEG_CONCURRENT_RPC | + NETLOGON_NEG_AVOID_ACCOUNT_DB_REPL | + NETLOGON_NEG_AVOID_SECURITYAUTH_DB_REPL | + NETLOGON_NEG_128BIT | + NETLOGON_NEG_TRANSITIVE_TRUSTS | + NETLOGON_NEG_DNS_DOMAIN_TRUSTS | + NETLOGON_NEG_PASSWORD_SET2 | + NETLOGON_NEG_GETDOMAININFO | + NETLOGON_NEG_CROSS_FOREST_TRUSTS | + NETLOGON_NEG_AUTHENTICATED_RPC_LSASS | + NETLOGON_NEG_SCHANNEL) +*/ enum schannel_direction { SENDER_IS_INITIATOR, diff --git a/source/lib/netapi/examples/netdomjoin-gui/netdomjoin-gui.c b/source/lib/netapi/examples/netdomjoin-gui/netdomjoin-gui.c index a3719c7..a4daf4f 100644 --- a/source/lib/netapi/examples/netdomjoin-gui/netdomjoin-gui.c +++ b/source/lib/netapi/examples/netdomjoin-gui/netdomjoin-gui.c @@ -63,14 +63,17 @@ typedef struct join_state { 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; @@ -111,10 +114,40 @@ static gboolean callback_delete_event(GtkWidget *widget, static void callback_do_close(GtkWidget *widget, gpointer data) { - debug("Closing now...\n"); + debug("callback_do_close called\n"); + gtk_widget_destroy(data); } +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(state->window_creds_prompt); + } +} + +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); + + gtk_widget_destroy(state->window_creds_prompt); + gtk_widget_destroy(state->window_do_change); +} + static void free_join_state(struct join_state *s) { SAFE_FREE(s->name_buffer_initial); @@ -155,6 +188,8 @@ static void callback_apply_description_change(GtkWidget *widget, GTK_BUTTONS_OK, "Failed to change computer description: %s.", libnetapi_get_error_string(state->ctx, status)); + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); + g_signal_connect_swapped(dialog, "response", G_CALLBACK(gtk_widget_destroy), dialog); @@ -183,6 +218,7 @@ static void callback_do_exit(GtkWidget *widget, 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: @@ -214,6 +250,7 @@ static void callback_do_reboot(GtkWidget *widget, 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); #if 0 g_signal_connect_swapped(dialog, "response", G_CALLBACK(gtk_widget_destroy), @@ -269,10 +306,14 @@ static void callback_return_username(GtkWidget *widget, { 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); @@ -287,7 +328,10 @@ static void callback_return_username_and_enter(GtkWidget *widget, return; } entry_text = gtk_entry_get_text(GTK_ENTRY(widget)); - debug("callback_return_username: %s\n", entry_text); + 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"); @@ -298,10 +342,14 @@ static void callback_return_password(GtkWidget *widget, { 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 @@ -320,16 +368,59 @@ static void callback_return_password_and_enter(GtkWidget *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); + debug("callback_return_password_and_enter: %s\n", entry_text); #else - debug("callback_return_password: (not printed)\n"); + 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, state); + callback_return_password(state->entry_password, state); + + gtk_widget_destroy(state->window_creds_prompt); +} + +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) { @@ -355,12 +446,112 @@ static void callback_do_hostname_change(GtkWidget *widget, GTK_BUTTONS_CLOSE, str); + gtk_window_set_modal(GTK_WINDOW(dialog), TRUE); 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); + + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(callback_do_close), window); + + 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) { @@ -372,16 +563,17 @@ static void callback_do_join(GtkWidget *widget, 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; - callback_return_username(state->entry_account, state); - callback_return_password(state->entry_password, state); - - if (state->window_creds_prompt) { - gtk_widget_destroy(GTK_WIDGET(state->window_creds_prompt)); + if (state->hostname_changed) { + callback_do_hostname_change(NULL, state); + return; } switch (state->name_type_initial) { @@ -406,8 +598,20 @@ static void callback_do_join(GtkWidget *widget, 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 = WKSSVC_JOIN_FLAGS_JOIN_TYPE | WKSSVC_JOIN_FLAGS_ACCOUNT_CREATE | WKSSVC_JOIN_FLAGS_DOMAIN_JOIN_IF_JOINED; /* for testing */ @@ -416,30 +620,36 @@ static void callback_do_join(GtkWidget *widget, if ((state->name_type_initial == NetSetupDomainName) && (state->name_type_new == NetSetupWorkgroupName)) { try_unjoin = TRUE; + unjoin_creds_required = TRUE; + join_creds_required = FALSE; unjoin_flags = WKSSVC_JOIN_FLAGS_JOIN_TYPE | WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE; } - 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"); 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(NULL, 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); @@ -452,18 +662,47 @@ static void callback_do_join(GtkWidget *widget, 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); } } + + 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 leave the domain.", + callback_do_storeauth_and_continue); + } + + if (!state->account || !state->password) { + debug("callback_do_join: still no creds???\n"); -- Samba Shared Repository