aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2014-03-07 08:46:44 +0000
committerWerner Koch <[email protected]>2014-03-07 08:48:10 +0000
commit5105c8d2d344fd7301d456d8c13c7e90a54f7e98 (patch)
treebf8c125202e32087ee5dacefb32fa09babdb1c46
parentagent: Fix binary vs. text mode problem in ssh. (diff)
downloadgnupg-5105c8d2d344fd7301d456d8c13c7e90a54f7e98.tar.gz
gnupg-5105c8d2d344fd7301d456d8c13c7e90a54f7e98.zip
ssh: Add support for Putty.
* agent/gpg-agent.c [W32]: Include Several Windows header. (opts): Change help text for enable-ssh-support. (opts, main): Add option --enable-putty-support (putty_support, PUTTY_IPC_MAGIC, PUTTY_IPC_MAXLEN): New for W32. (agent_init_default_ctrl): Add and asssert call. (putty_message_proc, putty_message_thread): New. (handle_connections) [W32]: Start putty message thread. * common/sysutils.c (w32_get_user_sid): New for W32 only * tools/gpgconf-comp.c (gc_options_gpg_agent): Add --enable-ssh-support and --enable-putty-support. Make the configuration group visible at basic level. * agent/command-ssh.c (serve_mmapped_ssh_request): New for W32 only. -- This patch enables support for Putty. It has been tested with Putty 0.62 using an Unix created ssh key copied to the private-keys-v1.d directory on Windows and with a manually crafted sshcontrol file. It also works with a smartcard key. May thanks to gniibe who implemented a proxy in Python to test the putty/gpg-agent communication. Signed-off-by: Werner Koch <[email protected]> (cherry picked from commit 9f32499f99a0817f63f7a73b09bdcebe60d4775d) Resolved conflicts: NEWS agent/agent.h agent/gpg-agent.c: Convert from pth to npth. common/sysutils.c common/sysutils.h
-rw-r--r--NEWS3
-rw-r--r--agent/agent.h11
-rw-r--r--agent/command-ssh.c146
-rw-r--r--agent/gpg-agent.c254
-rw-r--r--common/sysutils.c57
-rw-r--r--common/sysutils.h1
-rw-r--r--tools/gpgconf-comp.c8
7 files changed, 473 insertions, 7 deletions
diff --git a/NEWS b/NEWS
index 7eb1fe9e2..0c02a902e 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,9 @@ Noteworthy changes in version 2.1.0-betaN (unreleased)
* The GNU Pth library has been replaced by the new nPth library.
+ * New option --enable-putty-support to allow gpg-agent to act as a
+ Pageant replacement including full smartcard support.
+
* Removed support for the original HKP keyserver which is not anymore
used by any site.
diff --git a/agent/agent.h b/agent/agent.h
index d40930018..eac7ba548 100644
--- a/agent/agent.h
+++ b/agent/agent.h
@@ -289,9 +289,14 @@ gpg_error_t agent_print_status (ctrl_t ctrl, const char *keyword,
void bump_key_eventcounter (void);
void bump_card_eventcounter (void);
void start_command_handler (ctrl_t, gnupg_fd_t, gnupg_fd_t);
-gpg_error_t pinentry_loopback(ctrl_t, const char *keyword,
- unsigned char **buffer, size_t *size,
- size_t max_length);
+gpg_error_t pinentry_loopback (ctrl_t, const char *keyword,
+ unsigned char **buffer, size_t *size,
+ size_t max_length);
+
+#ifdef HAVE_W32_SYSTEM
+int serve_mmapped_ssh_request (ctrl_t ctrl,
+ unsigned char *request, size_t maxreqlen);
+#endif /*HAVE_W32_SYSTEM*/
/*-- command-ssh.c --*/
ssh_control_file_t ssh_open_control_file (void);
diff --git a/agent/command-ssh.c b/agent/command-ssh.c
index 46aa94c5e..4bd1aa7a7 100644
--- a/agent/command-ssh.c
+++ b/agent/command-ssh.c
@@ -3443,3 +3443,149 @@ start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client)
if (stream_sock)
es_fclose (stream_sock);
}
+
+
+#ifdef HAVE_W32_SYSTEM
+/* Serve one ssh-agent request. This is used for the Putty support.
+ REQUEST is the the mmapped memory which may be accessed up to a
+ length of MAXREQLEN. Returns 0 on success which also indicates
+ that a valid SSH response message is now in REQUEST. */
+int
+serve_mmapped_ssh_request (ctrl_t ctrl,
+ unsigned char *request, size_t maxreqlen)
+{
+ gpg_error_t err;
+ int send_err = 0;
+ int valid_response = 0;
+ ssh_request_spec_t *spec;
+ u32 msglen;
+ estream_t request_stream, response_stream;
+
+ if (setup_ssh_env (ctrl))
+ goto leave; /* Error setting up the environment. */
+
+ if (maxreqlen < 5)
+ goto leave; /* Caller error. */
+
+ msglen = uint32_construct (request[0], request[1], request[2], request[3]);
+ if (msglen < 1 || msglen > maxreqlen - 4)
+ {
+ log_error ("ssh message len (%u) out of range", (unsigned int)msglen);
+ goto leave;
+ }
+
+ spec = request_spec_lookup (request[4]);
+ if (!spec)
+ {
+ send_err = 1; /* Unknown request type. */
+ goto leave;
+ }
+
+ /* Create a stream object with the data part of the request. */
+ if (spec->secret_input)
+ request_stream = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+");
+ else
+ request_stream = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+");
+ if (!request_stream)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ /* We have to disable the estream buffering, because the estream
+ core doesn't know about secure memory. */
+ if (es_setvbuf (request_stream, NULL, _IONBF, 0))
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ /* Copy the request to the stream but omit the request type. */
+ err = stream_write_data (request_stream, request + 5, msglen - 1);
+ if (err)
+ goto leave;
+ es_rewind (request_stream);
+
+ response_stream = es_fopenmem (0, "r+b");
+ if (!response_stream)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ if (opt.verbose)
+ log_info ("ssh request handler for %s (%u) started\n",
+ spec->identifier, spec->type);
+
+ err = (*spec->handler) (ctrl, request_stream, response_stream);
+
+ if (opt.verbose)
+ {
+ if (err)
+ log_info ("ssh request handler for %s (%u) failed: %s\n",
+ spec->identifier, spec->type, gpg_strerror (err));
+ else
+ log_info ("ssh request handler for %s (%u) ready\n",
+ spec->identifier, spec->type);
+ }
+
+ es_fclose (request_stream);
+ request_stream = NULL;
+
+ if (err)
+ {
+ send_err = 1;
+ goto leave;
+ }
+
+ /* Put the response back into the mmapped buffer. */
+ {
+ void *response_data;
+ size_t response_size;
+
+ /* NB: In contrast to the request-stream, the response stream
+ includes the the message type byte. */
+ if (es_fclose_snatch (response_stream, &response_data, &response_size))
+ {
+ log_error ("snatching ssh response failed: %s",
+ gpg_strerror (gpg_error_from_syserror ()));
+ send_err = 1; /* Ooops. */
+ goto leave;
+ }
+
+ if (opt.verbose > 1)
+ log_info ("sending ssh response of length %u\n",
+ (unsigned int)response_size);
+ if (response_size > maxreqlen - 4)
+ {
+ log_error ("invalid length of the ssh response: %s",
+ gpg_strerror (GPG_ERR_INTERNAL));
+ es_free (response_data);
+ send_err = 1;
+ goto leave;
+ }
+
+ request[0] = response_size >> 24;
+ request[1] = response_size >> 16;
+ request[2] = response_size >> 8;
+ request[3] = response_size >> 0;
+ memcpy (request+4, response_data, response_size);
+ es_free (response_data);
+ valid_response = 1;
+ }
+
+ leave:
+ if (send_err)
+ {
+ request[0] = 0;
+ request[1] = 0;
+ request[2] = 0;
+ request[3] = 1;
+ request[4] = SSH_RESPONSE_FAILURE;
+ valid_response = 1;
+ }
+
+ /* Reset the SCD in case it has been used. */
+ agent_reset_scd (ctrl);
+
+ return valid_response? 0 : -1;
+}
+#endif /*HAVE_W32_SYSTEM*/
diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c
index c53566b2b..25750f288 100644
--- a/agent/gpg-agent.c
+++ b/agent/gpg-agent.c
@@ -1,6 +1,7 @@
/* gpg-agent.c - The GnuPG Agent
* Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2009,
* 2010 Free Software Foundation, Inc.
+ * Copyright (C) 2013 Werner Koch
*
* This file is part of GnuPG.
*
@@ -30,7 +31,16 @@
#include <time.h>
#include <fcntl.h>
#include <sys/stat.h>
-#ifndef HAVE_W32_SYSTEM
+#ifdef HAVE_W32_SYSTEM
+# ifndef WINVER
+# define WINVER 0x0500 /* Same as in common/sysutils.c */
+# endif
+# ifdef HAVE_WINSOCK2_H
+# include <winsock2.h>
+# endif
+# include <aclapi.h>
+# include <sddl.h>
+#else /*!HAVE_W32_SYSTEM*/
# include <sys/socket.h>
# include <sys/un.h>
#endif /*!HAVE_W32_SYSTEM*/
@@ -111,6 +121,7 @@ enum cmd_and_opt_values
oKeepTTY,
oKeepDISPLAY,
oSSHSupport,
+ oPuttySupport,
oDisableScdaemon,
oDisableCheckOwnSocket,
oWriteEnvFile
@@ -186,7 +197,14 @@ static ARGPARSE_OPTS opts[] = {
N_("allow presetting passphrase")},
{ oAllowLoopbackPinentry, "allow-loopback-pinentry", 0,
N_("allow presetting passphrase")},
- { oSSHSupport, "enable-ssh-support", 0, N_("enable ssh-agent emulation") },
+ { oSSHSupport, "enable-ssh-support", 0, N_("enable ssh support") },
+ { oPuttySupport, "enable-putty-support", 0,
+#ifdef HAVE_W32_SYSTEM
+ N_("enable putty support")
+#else
+ "@"
+#endif
+ },
{ oWriteEnvFile, "write-env-file", 2|8,
N_("|FILE|write environment settings also to FILE")},
{0}
@@ -218,6 +236,17 @@ static ARGPARSE_OPTS opts[] = {
#endif
+#ifdef HAVE_W32_SYSTEM
+/* Flag indicating that support for Putty has been enabled. */
+static int putty_support;
+/* A magic value used with WM_COPYDATA. */
+#define PUTTY_IPC_MAGIC 0x804e50ba
+/* To avoid surprises we limit the size of the mapped IPC file to this
+ value. Putty currently (0.62) uses 8k, thus 16k should be enough
+ for the foreseeable future. */
+#define PUTTY_IPC_MAXLEN 16384
+#endif /*HAVE_W32_SYSTEM*/
+
/* The list of open file descriptors at startup. Note that this list
has been allocated using the standard malloc. */
static int *startup_fd_list;
@@ -815,6 +844,13 @@ main (int argc, char **argv )
case oKeepDISPLAY: opt.keep_display = 1; break;
case oSSHSupport: opt.ssh_support = 1; break;
+ case oPuttySupport:
+# ifdef HAVE_W32_SYSTEM
+ putty_support = 1;
+ opt.ssh_support = 1;
+# endif
+ break;
+
case oWriteEnvFile:
if (pargs.r_type)
env_file_name = pargs.r.ret_str;
@@ -976,6 +1012,11 @@ main (int argc, char **argv )
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
es_printf ("disable-scdaemon:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
+#ifdef HAVE_W32_SYSTEM
+ es_printf ("enable-putty-support:%lu:\n", GC_OPT_FLAG_NONE);
+#else
+ es_printf ("enable-ssh-support:%lu:\n", GC_OPT_FLAG_NONE);
+#endif
agent_exit (0);
}
@@ -1311,6 +1352,8 @@ agent_exit (int rc)
static void
agent_init_default_ctrl (ctrl_t ctrl)
{
+ assert (ctrl->session_env);
+
/* Note we ignore malloc errors because we can't do much about it
and the request will fail anyway shortly after this
initialization. */
@@ -1328,7 +1371,6 @@ agent_init_default_ctrl (ctrl_t ctrl)
xfree (ctrl->lc_messages);
ctrl->lc_messages = default_lc_messages? xtrystrdup (default_lc_messages)
/**/ : NULL;
-
ctrl->cache_ttl_opt_preset = CACHE_TTL_OPT_PRESET;
}
@@ -1820,6 +1862,196 @@ check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce)
}
+#ifdef HAVE_W32_SYSTEM
+/* The window message processing function for Putty. Warning: This
+ code runs as a native Windows thread. Use of our own functions
+ needs to be bracket with pth_leave/pth_enter. */
+static LRESULT CALLBACK
+putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+ int ret = 0;
+ int w32rc;
+ COPYDATASTRUCT *cds;
+ const char *mapfile;
+ HANDLE maphd;
+ PSID mysid = NULL;
+ PSID mapsid = NULL;
+ void *data = NULL;
+ PSECURITY_DESCRIPTOR psd = NULL;
+ ctrl_t ctrl = NULL;
+
+ if (msg != WM_COPYDATA)
+ {
+ return DefWindowProc (hwnd, msg, wparam, lparam);
+ }
+
+ cds = (COPYDATASTRUCT*)lparam;
+ if (cds->dwData != PUTTY_IPC_MAGIC)
+ return 0; /* Ignore data with the wrong magic. */
+ mapfile = cds->lpData;
+ if (!cds->cbData || mapfile[cds->cbData - 1])
+ return 0; /* Ignore empty and non-properly terminated strings. */
+
+ if (DBG_ASSUAN)
+ {
+ npth_protect ();
+ log_debug ("ssh map file '%s'", mapfile);
+ npth_unprotect ();
+ }
+
+ maphd = OpenFileMapping (FILE_MAP_ALL_ACCESS, FALSE, mapfile);
+ if (DBG_ASSUAN)
+ {
+ npth_protect ();
+ log_debug ("ssh map handle %p\n", maphd);
+ npth_unprotect ();
+ }
+
+ if (!maphd || maphd == INVALID_HANDLE_VALUE)
+ return 0;
+
+ npth_protect ();
+
+ mysid = w32_get_user_sid ();
+ if (!mysid)
+ {
+ log_error ("error getting my sid\n");
+ goto leave;
+ }
+
+ w32rc = GetSecurityInfo (maphd, SE_KERNEL_OBJECT,
+ OWNER_SECURITY_INFORMATION,
+ &mapsid, NULL, NULL, NULL,
+ &psd);
+ if (w32rc)
+ {
+ log_error ("error getting sid of ssh map file: rc=%d", w32rc);
+ goto leave;
+ }
+
+ if (DBG_ASSUAN)
+ {
+ char *sidstr;
+
+ if (!ConvertSidToStringSid (mysid, &sidstr))
+ sidstr = NULL;
+ log_debug (" my sid: '%s'", sidstr? sidstr: "[error]");
+ LocalFree (sidstr);
+ if (!ConvertSidToStringSid (mapsid, &sidstr))
+ sidstr = NULL;
+ log_debug ("ssh map file sid: '%s'", sidstr? sidstr: "[error]");
+ LocalFree (sidstr);
+ }
+
+ if (!EqualSid (mysid, mapsid))
+ {
+ log_error ("ssh map file has a non-matching sid\n");
+ goto leave;
+ }
+
+ data = MapViewOfFile (maphd, FILE_MAP_ALL_ACCESS, 0, 0, 0);
+ if (DBG_ASSUAN)
+ log_debug ("ssh IPC buffer at %p\n", data);
+ if (!data)
+ goto leave;
+
+ /* log_printhex ("request:", data, 20); */
+
+ ctrl = xtrycalloc (1, sizeof *ctrl);
+ if (!ctrl)
+ {
+ log_error ("error allocating connection control data: %s\n",
+ strerror (errno) );
+ goto leave;
+ }
+ ctrl->session_env = session_env_new ();
+ if (!ctrl->session_env)
+ {
+ log_error ("error allocating session environment block: %s\n",
+ strerror (errno) );
+ goto leave;
+ }
+
+ agent_init_default_ctrl (ctrl);
+ if (!serve_mmapped_ssh_request (ctrl, data, PUTTY_IPC_MAXLEN))
+ ret = 1; /* Valid ssh message has been constructed. */
+ agent_deinit_default_ctrl (ctrl);
+ /* log_printhex (" reply:", data, 20); */
+
+ leave:
+ xfree (ctrl);
+ if (data)
+ UnmapViewOfFile (data);
+ xfree (mapsid);
+ if (psd)
+ LocalFree (psd);
+ xfree (mysid);
+ CloseHandle (maphd);
+
+ npth_unprotect ();
+
+ return ret;
+}
+#endif /*HAVE_W32_SYSTEM*/
+
+
+#ifdef HAVE_W32_SYSTEM
+/* The thread handling Putty's IPC requests. */
+static void *
+putty_message_thread (void *arg)
+{
+ WNDCLASS wndwclass = {0, putty_message_proc, 0, 0,
+ NULL, NULL, NULL, NULL, NULL, "Pageant"};
+ HWND hwnd;
+ MSG msg;
+
+ (void)arg;
+
+ if (opt.verbose)
+ log_info ("putty message loop thread started\n");
+
+ /* The message loop runs as thread independent from our nPth system.
+ This also means that we need to make sure that we switch back to
+ our system before calling any no-windows function. */
+ npth_unprotect ();
+
+ /* First create a window to make sure that a message queue exists
+ for this thread. */
+ if (!RegisterClass (&wndwclass))
+ {
+ npth_protect ();
+ log_error ("error registering Pageant window class");
+ return NULL;
+ }
+ hwnd = CreateWindowEx (0, "Pageant", "Pageant", 0,
+ 0, 0, 0, 0,
+ HWND_MESSAGE, /* hWndParent */
+ NULL, /* hWndMenu */
+ NULL, /* hInstance */
+ NULL); /* lpParm */
+ if (!hwnd)
+ {
+ npth_protect ();
+ log_error ("error creating Pageant window");
+ return NULL;
+ }
+
+ while (GetMessage(&msg, NULL, 0, 0))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ /* Back to nPth. */
+ npth_protect ();
+
+ if (opt.verbose)
+ log_info ("putty message loop thread stopped\n");
+ return NULL;
+}
+#endif /*HAVE_W32_SYSTEM*/
+
+
/* This is the standard connection thread's main function. */
static void *
start_connection_thread (void *arg)
@@ -1920,6 +2152,22 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_ssh)
# endif
#endif
+ /* On Windows we need to fire up a separate thread to listen for
+ requests from Putty (an SSH client), so we can replace Putty's
+ Pageant (its ssh-agent implementation). */
+#ifdef HAVE_W32_SYSTEM
+ if (putty_support)
+ {
+ npth_t thread;
+
+ ret = npth_create (&thread, &tattr, putty_message_thread, NULL);
+ if (ret)
+ {
+ log_error ("error spawning putty message loop: %s\n", strerror (ret));
+ }
+ }
+#endif /*HAVE_W32_SYSTEM*/
+
/* Set a flag to tell call-scd.c that it may enable event
notifications. */
opt.sigusr2_enabled = 1;
diff --git a/common/sysutils.c b/common/sysutils.c
index f57dcc1f3..a00cd94d1 100644
--- a/common/sysutils.c
+++ b/common/sysutils.c
@@ -1,6 +1,7 @@
/* sysutils.c - system helpers
* Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004,
* 2007, 2008 Free Software Foundation, Inc.
+ * Copyright (C) 2013 Werner Koch
*
* This file is part of GnuPG.
*
@@ -688,3 +689,59 @@ _gnupg_getenv (const char *name)
}
#endif /*HAVE_W32CE_SYSTEM*/
+
+
+#ifdef HAVE_W32_SYSTEM
+/* Return the user's security identifier from the current process. */
+PSID
+w32_get_user_sid (void)
+{
+ int okay = 0;
+ HANDLE proc = NULL;
+ HANDLE token = NULL;
+ TOKEN_USER *user = NULL;
+ PSID sid = NULL;
+ DWORD tokenlen, sidlen;
+
+ proc = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
+ if (!proc)
+ goto leave;
+
+ if (!OpenProcessToken (proc, TOKEN_QUERY, &token))
+ goto leave;
+
+ if (!GetTokenInformation (token, TokenUser, NULL, 0, &tokenlen)
+ && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+ goto leave;
+
+ user = xtrymalloc (tokenlen);
+ if (!user)
+ goto leave;
+
+ if (!GetTokenInformation (token, TokenUser, user, tokenlen, &tokenlen))
+ goto leave;
+ if (!IsValidSid (user->User.Sid))
+ goto leave;
+ sidlen = GetLengthSid (user->User.Sid);
+ sid = xtrymalloc (sidlen);
+ if (!sid)
+ goto leave;
+ if (!CopySid (sidlen, sid, user->User.Sid))
+ goto leave;
+ okay = 1;
+
+ leave:
+ xfree (user);
+ if (token)
+ CloseHandle (token);
+ if (proc)
+ CloseHandle (proc);
+
+ if (!okay)
+ {
+ xfree (sid);
+ sid = NULL;
+ }
+ return sid;
+}
+#endif /*HAVE_W32_SYSTEM*/
diff --git a/common/sysutils.h b/common/sysutils.h
index 5faa7b37d..da2c2509d 100644
--- a/common/sysutils.h
+++ b/common/sysutils.h
@@ -65,6 +65,7 @@ int gnupg_setenv (const char *name, const char *value, int overwrite);
int gnupg_unsetenv (const char *name);
#ifdef HAVE_W32_SYSTEM
+void *w32_get_user_sid (void);
#include "../common/w32help.h"
diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c
index dda4c9a3f..e97c7bd51 100644
--- a/tools/gpgconf-comp.c
+++ b/tools/gpgconf-comp.c
@@ -492,7 +492,7 @@ static gc_option_t gc_options_gpg_agent[] =
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "Configuration",
- GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
+ GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the configuration") },
{ "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
"gnupg", "|FILE|read options from FILE",
@@ -500,6 +500,12 @@ static gc_option_t gc_options_gpg_agent[] =
{ "disable-scdaemon", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
"gnupg", "do not use the SCdaemon",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
+ { "enable-ssh-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
+ "gnupg", "enable ssh support",
+ GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
+ { "enable-putty-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
+ "gnupg", "enable putty support",
+ GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "Debug",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,