diff --git a/.gitignore b/.gitignore index 9d812531726..8b132cb5fd1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ *.orig *.rej **/.keep +*.sdf +*.suo +*.vcxproj.user .idea .svn .classpath diff --git a/hadoop-common-project/hadoop-common/pom.xml b/hadoop-common-project/hadoop-common/pom.xml index 6cdced263b3..3e7bf0c05a3 100644 --- a/hadoop-common-project/hadoop-common/pom.xml +++ b/hadoop-common-project/hadoop-common/pom.xml @@ -33,6 +33,8 @@ common true + ../etc/hadoop + wsce-site.xml @@ -735,6 +737,9 @@ /nologo /p:Configuration=Release /p:OutDir=${project.build.directory}/bin/ + /p:IntermediateOutputPath=${project.build.directory}/winutils/ + /p:WsceConfigDir=${wsce.config.dir} + /p:WsceConfigFile=${wsce.config.file} diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java index 0848cb1cd4c..91f00e18e3f 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/FileUtil.java @@ -1186,6 +1186,11 @@ public static String[] list(File dir) throws IOException { return fileNames; } + public static String[] createJarWithClassPath(String inputClassPath, Path pwd, + Map callerEnv) throws IOException { + return createJarWithClassPath(inputClassPath, pwd, pwd, callerEnv); + } + /** * Create a jar file at the given path, containing a manifest with a classpath * that references all specified entries. @@ -1210,6 +1215,7 @@ public static String[] list(File dir) throws IOException { * * @param inputClassPath String input classpath to bundle into the jar manifest * @param pwd Path to working directory to save jar + * @param targetDir path to where the jar execution will have its working dir * @param callerEnv Map caller's environment variables to use * for expansion * @return String[] with absolute path to new jar in position 0 and @@ -1217,6 +1223,7 @@ public static String[] list(File dir) throws IOException { * @throws IOException if there is an I/O error while writing the jar file */ public static String[] createJarWithClassPath(String inputClassPath, Path pwd, + Path targetDir, Map callerEnv) throws IOException { // Replace environment variables, case-insensitive on Windows @SuppressWarnings("unchecked") @@ -1265,7 +1272,7 @@ public static String[] createJarWithClassPath(String inputClassPath, Path pwd, // Append just this entry File fileCpEntry = null; if(!new Path(classPathEntry).isAbsolute()) { - fileCpEntry = new File(workingDir, classPathEntry); + fileCpEntry = new File(targetDir.toString(), classPathEntry); } else { fileCpEntry = new File(classPathEntry); diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/RawLocalFileSystem.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/RawLocalFileSystem.java index a9e02da1734..ca815a3517e 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/RawLocalFileSystem.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/RawLocalFileSystem.java @@ -268,7 +268,12 @@ private FSDataOutputStream create(Path f, boolean overwrite, throw new IOException("Mkdirs failed to create " + parent.toString()); } return new FSDataOutputStream(new BufferedOutputStream( - new LocalFSFileOutputStream(f, false), bufferSize), statistics); + createOutputStream(f, false), bufferSize), statistics); + } + + protected OutputStream createOutputStream(Path f, boolean append) + throws IOException { + return new LocalFSFileOutputStream(f, append); } @Override @@ -400,6 +405,10 @@ public FileStatus[] listStatus(Path f) throws IOException { } return Arrays.copyOf(results, j); } + + protected boolean mkOneDir(File p2f) throws IOException { + return p2f.mkdir(); + } /** * Creates the specified directory hierarchy. Does not @@ -412,8 +421,9 @@ public boolean mkdirs(Path f) throws IOException { } Path parent = f.getParent(); File p2f = pathToFile(f); + File parent2f = null; if(parent != null) { - File parent2f = pathToFile(parent); + parent2f = pathToFile(parent); if(parent2f != null && parent2f.exists() && !parent2f.isDirectory()) { throw new ParentNotDirectoryException("Parent path is not a directory: " + parent); @@ -423,8 +433,8 @@ public boolean mkdirs(Path f) throws IOException { throw new FileNotFoundException("Destination exists" + " and is not a directory: " + p2f.getCanonicalPath()); } - return (parent == null || mkdirs(parent)) && - (p2f.mkdir() || p2f.isDirectory()); + return (parent == null || parent2f.exists() || mkdirs(parent)) && + (mkOneDir(p2f) || p2f.isDirectory()); } @Override diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/nativeio/NativeIO.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/nativeio/NativeIO.java index 43f1cb17a5f..24009586a31 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/nativeio/NativeIO.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/nativeio/NativeIO.java @@ -22,6 +22,8 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.RandomAccessFile; import java.lang.reflect.Field; import java.nio.ByteBuffer; @@ -35,6 +37,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.CommonConfigurationKeys; import org.apache.hadoop.fs.HardLink; +import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.SecureIOUtils.AlreadyExistsException; import org.apache.hadoop.util.NativeCodeLoader; import org.apache.hadoop.util.Shell; @@ -504,6 +507,8 @@ public static class Windows { public static final long FILE_BEGIN = 0; public static final long FILE_CURRENT = 1; public static final long FILE_END = 2; + + public static final long FILE_ATTRIBUTE_NORMAL = 0x00000080L; /** Wrapper around CreateFile() on Windows */ public static native FileDescriptor createFile(String path, diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/Shell.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/Shell.java index b78d791d592..bd25b9d742f 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/Shell.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/Shell.java @@ -649,6 +649,18 @@ public String toString() { } } + public interface CommandExecutor { + + void execute() throws IOException; + + int getExitCode() throws IOException; + + String getOutput() throws IOException; + + void close(); + + } + /** * A simple shell command executor. * @@ -657,7 +669,8 @@ public String toString() { * directory and the environment remains unchanged. The output of the command * is stored as-is and is expected to be small. */ - public static class ShellCommandExecutor extends Shell { + public static class ShellCommandExecutor extends Shell + implements CommandExecutor { private String[] command; private StringBuffer output; @@ -749,6 +762,10 @@ public String toString() { } return builder.toString(); } + + @Override + public void close() { + } } /** diff --git a/hadoop-common-project/hadoop-common/src/main/native/native.vcxproj b/hadoop-common-project/hadoop-common/src/main/native/native.vcxproj index 0d67e1ee940..2d60e562bc2 100644 --- a/hadoop-common-project/hadoop-common/src/main/native/native.vcxproj +++ b/hadoop-common-project/hadoop-common/src/main/native/native.vcxproj @@ -107,6 +107,9 @@ /D HADOOP_SNAPPY_LIBRARY=L\"snappy.dll\" + + src\org\apache\hadoop\io\nativeio;%(AdditionalIncludeDirectories) + @@ -120,6 +123,7 @@ + diff --git a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/io/nativeio/NativeIO.c b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/io/nativeio/NativeIO.c index f19d6bee81a..792205d5a9e 100644 --- a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/io/nativeio/NativeIO.c +++ b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/io/nativeio/NativeIO.c @@ -71,8 +71,13 @@ static jmethodID nioe_ctor; // Please see HADOOP-7156 for details. jobject pw_lock_object; +/* + * Throw a java.IO.IOException, generating the message from errno. + * NB. this is also used form windows_secure_container_executor.c + */ +extern void throw_ioe(JNIEnv* env, int errnum); + // Internal functions -static void throw_ioe(JNIEnv* env, int errnum); #ifdef UNIX static ssize_t get_pw_buflen(); #endif @@ -216,7 +221,7 @@ static int map_fadvise_flag(jint flag) { */ JNIEXPORT void JNICALL Java_org_apache_hadoop_io_nativeio_NativeIO_initNative( - JNIEnv *env, jclass clazz) { + JNIEnv *env, jclass clazz) { stat_init(env, clazz); PASS_EXCEPTIONS_GOTO(env, error); nioe_init(env); @@ -802,7 +807,7 @@ cleanup: /* * Throw a java.IO.IOException, generating the message from errno. */ -static void throw_ioe(JNIEnv* env, int errnum) +void throw_ioe(JNIEnv* env, int errnum) { #ifdef UNIX char message[80]; diff --git a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/yarn/server/nodemanager/windows_secure_container_executor.c b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/yarn/server/nodemanager/windows_secure_container_executor.c new file mode 100644 index 00000000000..7e6506571eb --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/yarn/server/nodemanager/windows_secure_container_executor.c @@ -0,0 +1,569 @@ +/** +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with this +* work for additional information regarding copyright ownership. The ASF +* licenses this file to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations under +* the License. +*/ + +#include +#include "org_apache_hadoop.h" +#include "windows_secure_container_executor.h" +#include "winutils.h" +#include "file_descriptor.h" + +// class of org.apache.hadoop.yarn.server.nodemanager.WindowsSecureContainerExecutor.Native.WinutilsProcessStub +static jclass wps_class = NULL; + +/* + * private static native void initWsceNative(); + * + * We rely on this function rather than lazy initialization because + * the lazy approach may have a race if multiple callers try to + * init at the same time. + */ +JNIEXPORT void JNICALL +Java_org_apache_hadoop_yarn_server_nodemanager_WindowsSecureContainerExecutor_00024Native_initWsceNative( + JNIEnv *env, jclass clazz) { + +#ifdef WINDOWS + winutils_process_stub_init(env); + PASS_EXCEPTIONS_GOTO(env, error); +#endif + + return; +error: + // these are all idempodent and safe to call even if the + // class wasn't initted yet +#ifdef WINDOWS + winutils_process_stub_deinit(env); +#endif +} + +static jmethodID wps_constructor = NULL; +static jfieldID wps_hProcess = NULL; +static jfieldID wps_hThread = NULL; +static jfieldID wps_disposed = NULL; + +extern void throw_ioe(JNIEnv* env, int errnum); + +void winutils_process_stub_init(JNIEnv *env) { + if (wps_class != NULL) return; // already initted + + wps_class = (*env)->FindClass(env, WINUTILS_PROCESS_STUB_CLASS); + PASS_EXCEPTIONS(env); + + wps_class = (*env)->NewGlobalRef(env, wps_class); + PASS_EXCEPTIONS(env); + + wps_hProcess = (*env)->GetFieldID(env, wps_class, "hProcess", "J"); + PASS_EXCEPTIONS(env); + + wps_hThread = (*env)->GetFieldID(env, wps_class, "hThread", "J"); + PASS_EXCEPTIONS(env); + + wps_disposed = (*env)->GetFieldID(env, wps_class, "disposed", "Z"); + PASS_EXCEPTIONS(env); + + wps_constructor = (*env)->GetMethodID(env, wps_class, "", "(JJJJJ)V"); + PASS_EXCEPTIONS(env); + + LogDebugMessage(L"winutils_process_stub_init\n"); +} + +void winutils_process_stub_deinit(JNIEnv *env) { + if (wps_class != NULL) { + (*env)->DeleteGlobalRef(env, wps_class); + wps_class = NULL; + } + wps_hProcess = NULL; + wps_hThread = NULL; + wps_disposed = NULL; + wps_constructor = NULL; + LogDebugMessage(L"winutils_process_stub_deinit\n"); +} + +jobject winutils_process_stub_create(JNIEnv *env, + jlong hProcess, jlong hThread, jlong hStdIn, jlong hStdOut, jlong hStdErr) { + jobject obj = (*env)->NewObject(env, wps_class, wps_constructor, + hProcess, hThread, hStdIn, hStdOut, hStdErr); + PASS_EXCEPTIONS_RET(env, NULL); + + LogDebugMessage(L"winutils_process_stub_create: %p\n", obj); + + return obj; +} + + +/* + * Class: org.apache.hadoop.yarn.server.nodemanager.WindowsSecureContainerExecutor$Native + * Method: createTaskAsUser + * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String)Lorg/apache/hadoop/io/nativeio/NativeIO$WinutilsProcessStub + */ +JNIEXPORT jobject JNICALL +Java_org_apache_hadoop_yarn_server_nodemanager_WindowsSecureContainerExecutor_00024Native_createTaskAsUser0(JNIEnv* env, + jclass clazz, jstring jcwd, jstring jjobName, jstring juser, jstring jpidFile, jstring jcmdLine) { +#ifdef UNIX + THROW(env, "java/io/IOException", + "The function createTaskAsUser is not supported on Unix"); + return NULL; +#endif + +#ifdef WINDOWS + LPCWSTR cwd = NULL, jobName = NULL, + user = NULL, pidFile = NULL, cmdLine = NULL; + DWORD dwError = ERROR_SUCCESS; + HANDLE hProcess = INVALID_HANDLE_VALUE, + hThread = INVALID_HANDLE_VALUE, + hStdIn = INVALID_HANDLE_VALUE, + hStdOut = INVALID_HANDLE_VALUE, + hStdErr = INVALID_HANDLE_VALUE; + jobject ret = NULL; + + cwd = (LPCWSTR) (*env)->GetStringChars(env, jcwd, NULL); + if (!cwd) goto done; // exception was thrown + + jobName = (LPCWSTR) (*env)->GetStringChars(env, jjobName, NULL); + if (!jobName) goto done; // exception was thrown + + user = (LPCWSTR) (*env)->GetStringChars(env, juser, NULL); + if (!user) goto done; // exception was thrown + + pidFile = (LPCWSTR) (*env)->GetStringChars(env, jpidFile, NULL); + if (!pidFile) goto done; // exception was thrown + + cmdLine = (LPCWSTR) (*env)->GetStringChars(env, jcmdLine, NULL); + if (!cmdLine) goto done; // exception was thrown + + LogDebugMessage(L"createTaskAsUser: jcwd:%s job:%s juser:%s pid:%s cmd:%s\n", + cwd, jobName, user, pidFile, cmdLine); + + dwError = RpcCall_TaskCreateAsUser(cwd, jobName, user, pidFile, cmdLine, + &hProcess, &hThread, &hStdIn, &hStdOut, &hStdErr); + + if (ERROR_SUCCESS == dwError) { + ret = winutils_process_stub_create(env, (jlong) hProcess, (jlong) hThread, + (jlong) hStdIn, (jlong) hStdOut, (jlong) hStdErr); + + if (NULL == ret) { + TerminateProcess(hProcess, EXIT_FAILURE); + CloseHandle(hThread); + CloseHandle(hProcess); + CloseHandle(hStdIn); + CloseHandle(hStdOut); + CloseHandle(hStdErr); + } + } + + + if (dwError != ERROR_SUCCESS) { + throw_ioe (env, dwError); + } + +done: + + if (cwd) (*env)->ReleaseStringChars(env, jcwd, cwd); + if (jobName) (*env)->ReleaseStringChars(env, jjobName, jobName); + if (user) (*env)->ReleaseStringChars(env, juser, user); + if (pidFile) (*env)->ReleaseStringChars(env, jpidFile, pidFile); + if (cmdLine) (*env)->ReleaseStringChars(env, jcmdLine, cmdLine); + + return ret; + +#endif +} + +/* + * Class: org.apache.hadoop.yarn.server.nodemanager.WindowsSecureContainerExecutor$Native$Elevated + * Method: elevatedKillTaskImpl + * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL +Java_org_apache_hadoop_yarn_server_nodemanager_WindowsSecureContainerExecutor_00024Native_00024Elevated_elevatedKillTaskImpl(JNIEnv* env, + jclass clazz, jstring jtask_name) { +#ifdef UNIX + THROW(env, "java/io/IOException", + "The function elevatedSetOwner0 is not supported on Unix"); + return; +#endif + +#ifdef WINDOWS + + LPCWSTR task_name = NULL; + DWORD dwError; + + task_name = (LPCWSTR) (*env)->GetStringChars(env, jtask_name, NULL); + if (!task_name) goto done; // exception was thrown + + + dwError = RpcCall_WinutilsKillTask(task_name); + + if (dwError != ERROR_SUCCESS) { + throw_ioe (env, dwError); + } + +done: + if (task_name) (*env)->ReleaseStringChars(env, jtask_name, task_name); + +#endif + +} + + +/* + * Class: org.apache.hadoop.yarn.server.nodemanager.WindowsSecureContainerExecutor$Native$Elevated + * Method: elevatedChownImpl + * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL +Java_org_apache_hadoop_yarn_server_nodemanager_WindowsSecureContainerExecutor_00024Native_00024Elevated_elevatedChownImpl(JNIEnv* env, + jclass clazz, jstring jpath, jstring juser, jstring jgroup) { +#ifdef UNIX + THROW(env, "java/io/IOException", + "The function elevatedSetOwner0 is not supported on Unix"); + return; +#endif + +#ifdef WINDOWS + + LPCWSTR path = NULL, user = NULL, group = NULL; + DWORD dwError; + + path = (LPCWSTR) (*env)->GetStringChars(env, jpath, NULL); + if (!path) goto done; // exception was thrown + + if (juser) { + user = (LPCWSTR) (*env)->GetStringChars(env, juser, NULL); + if (!user) goto done; // exception was thrown + } + + if (jgroup) { + group = (LPCWSTR) (*env)->GetStringChars(env, jgroup, NULL); + if (!group) goto done; // exception was thrown + } + + dwError = RpcCall_WinutilsChown(path, user, group); + + if (dwError != ERROR_SUCCESS) { + throw_ioe (env, dwError); + } + +done: + if (path) (*env)->ReleaseStringChars(env, jpath, path); + if (user) (*env)->ReleaseStringChars(env, juser, user); + if (group) (*env)->ReleaseStringChars(env, jgroup, group); + +#endif + +} + + +/* + * Class: org.apache.hadoop.yarn.server.nodemanager.WindowsSecureContainerExecutor$Native$Elevated + * Method: elevatedMkDirImpl + * Signature: (Ljava/lang/String;)V + */ +JNIEXPORT void JNICALL +Java_org_apache_hadoop_yarn_server_nodemanager_WindowsSecureContainerExecutor_00024Native_00024Elevated_elevatedMkDirImpl(JNIEnv* env, + jclass clazz, jstring jpath) { +#ifdef UNIX + THROW(env, "java/io/IOException", + "The function elevatedMkDirImpl is not supported on Unix"); + return; +#endif + +#ifdef WINDOWS + + LPCWSTR path = NULL, user = NULL, group = NULL; + DWORD dwError; + + path = (LPCWSTR) (*env)->GetStringChars(env, jpath, NULL); + if (!path) goto done; // exception was thrown + + dwError = RpcCall_WinutilsMkDir(path); + + if (dwError != ERROR_SUCCESS) { + throw_ioe (env, dwError); + } + +done: + if (path) (*env)->ReleaseStringChars(env, jpath, path); + +#endif + +} + + +/* + * Class: org.apache.hadoop.yarn.server.nodemanager.WindowsSecureContainerExecutor$Native$Elevated + * Method: elevatedChmodImpl + * Signature: (Ljava/lang/String;I)V + */ +JNIEXPORT void JNICALL +Java_org_apache_hadoop_yarn_server_nodemanager_WindowsSecureContainerExecutor_00024Native_00024Elevated_elevatedChmodImpl(JNIEnv* env, + jclass clazz, jstring jpath, jint jmode) { +#ifdef UNIX + THROW(env, "java/io/IOException", + "The function elevatedChmodImpl is not supported on Unix"); + return; +#endif + +#ifdef WINDOWS + + LPCWSTR path = NULL; + DWORD dwError; + + path = (LPCWSTR) (*env)->GetStringChars(env, jpath, NULL); + if (!path) goto done; // exception was thrown + + dwError = RpcCall_WinutilsChmod(path, (int) jmode); + + if (dwError != ERROR_SUCCESS) { + throw_ioe (env, dwError); + } + +done: + if (path) (*env)->ReleaseStringChars(env, jpath, path); + +#endif + +} + + +/* + * Class: org.apache.hadoop.yarn.server.nodemanager.WindowsSecureContainerExecutor$Native$Elevated + * Method: elevatedCopyImpl + * Signature: (I;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V + */ +JNIEXPORT void JNICALL +Java_org_apache_hadoop_yarn_server_nodemanager_WindowsSecureContainerExecutor_00024Native_00024Elevated_elevatedCopyImpl(JNIEnv* env, + jclass clazz, jint joperation, jstring jsourcePath, jstring jdestinationPath, jboolean replaceExisting) { +#ifdef UNIX + THROW(env, "java/io/IOException", + "The function elevatedCopyImpl is not supported on Unix"); + return; +#endif + +#ifdef WINDOWS + + LPCWSTR sourcePath = NULL, destinationPath = NULL; + DWORD dwError; + + sourcePath = (LPCWSTR) (*env)->GetStringChars(env, jsourcePath, NULL); + if (!sourcePath) goto done; // exception was thrown + + destinationPath = (LPCWSTR) (*env)->GetStringChars(env, jdestinationPath, NULL); + if (!destinationPath) goto done; // exception was thrown + + dwError = RpcCall_WinutilsMoveFile((INT) joperation, sourcePath, destinationPath, (BOOL) replaceExisting); + + if (dwError != ERROR_SUCCESS) { + throw_ioe (env, dwError); + } + +done: + if (sourcePath) (*env)->ReleaseStringChars(env, jsourcePath, sourcePath); + if (destinationPath) (*env)->ReleaseStringChars(env, jdestinationPath, destinationPath); +#endif +} + +/* + * Class: org.apache.hadoop.yarn.server.nodemanager.WindowsSecureContainerExecutor$Native$Elevated + * Method: elevatedCreateImpl + * Signature: (Ljava/lang/String;J;J;J;J)J + */ +JNIEXPORT jlong JNICALL +Java_org_apache_hadoop_yarn_server_nodemanager_WindowsSecureContainerExecutor_00024Native_00024Elevated_elevatedCreateImpl(JNIEnv* env, + jclass clazz, jstring jpath, jlong jdesired_access, jlong jshare_mode, jlong jcreation_disposition, jlong jflags) { +#ifdef UNIX + THROW(env, "java/io/IOException", + "The function elevatedCreateImpl is not supported on Unix"); + return 0; +#endif + +#ifdef WINDOWS + + LPCWSTR path = NULL; + DWORD dwError; + HANDLE hFile = INVALID_HANDLE_VALUE; + + path = (LPCWSTR) (*env)->GetStringChars(env, jpath, NULL); + if (!path) goto done; // exception was thrown + + dwError = RpcCall_WinutilsCreateFile(path, + (DWORD) jdesired_access, (DWORD) jshare_mode, (DWORD) jcreation_disposition, (DWORD) jflags, + &hFile); + + if (dwError != ERROR_SUCCESS) { + throw_ioe (env, dwError); + } + +done: + if (path) (*env)->ReleaseStringChars(env, jpath, path); + return hFile; +#endif +} + +/* + * Class: org.apache.hadoop.yarn.server.nodemanager.WindowsSecureContainerExecutor$Native$Elevated + * Method: elevatedDeletePathImpl + * Signature: (Ljava/lang/String;Z)Z + */ +JNIEXPORT jboolean JNICALL +Java_org_apache_hadoop_yarn_server_nodemanager_WindowsSecureContainerExecutor_00024Native_00024Elevated_elevatedDeletePathImpl(JNIEnv* env, + jclass clazz, jstring jpath, jboolean jIsDir) { +#ifdef UNIX + THROW(env, "java/io/IOException", + "The function elevatedDeleteFileImpl is not supported on Unix"); + return JNI_FALSE; +#endif + +#ifdef WINDOWS + + LPCWSTR path = NULL; + DWORD dwError; + BOOL deleted = FALSE; + + path = (LPCWSTR) (*env)->GetStringChars(env, jpath, NULL); + if (!path) goto done; // exception was thrown + + dwError = RpcCall_WinutilsDeletePath(path, (BOOL) jIsDir, &deleted); + + if (dwError != ERROR_SUCCESS) { + throw_ioe (env, dwError); + } + +done: + if (path) (*env)->ReleaseStringChars(env, jpath, path); + return (jboolean) deleted; +#endif +} + + + + +/* + * native void destroy(); + * + * The "00024" in the function name is an artifact of how JNI encodes + * special characters. U+0024 is '$'. + */ +JNIEXPORT void JNICALL +Java_org_apache_hadoop_yarn_server_nodemanager_WindowsSecureContainerExecutor_00024Native_00024WinutilsProcessStub_destroy( + JNIEnv *env, jobject objSelf) { + + HANDLE hProcess = (HANDLE)(*env)->GetLongField(env, objSelf, wps_hProcess); + LogDebugMessage(L"TerminateProcess: %x\n", hProcess); + TerminateProcess(hProcess, EXIT_FAILURE); +} + +/* + * native void waitFor(); + * + * The "00024" in the function name is an artifact of how JNI encodes + * special characters. U+0024 is '$'. + */ +JNIEXPORT void JNICALL +Java_org_apache_hadoop_yarn_server_nodemanager_WindowsSecureContainerExecutor_00024Native_00024WinutilsProcessStub_waitFor( + JNIEnv *env, jobject objSelf) { + + HANDLE hProcess = (HANDLE)(*env)->GetLongField(env, objSelf, wps_hProcess); + LogDebugMessage(L"WaitForSingleObject: %x\n", hProcess); + WaitForSingleObject(hProcess, INFINITE); +} + + + +/* + * native void resume(); + * + * The "00024" in the function name is an artifact of how JNI encodes + * special characters. U+0024 is '$'. + */ +JNIEXPORT void JNICALL +Java_org_apache_hadoop_yarn_server_nodemanager_WindowsSecureContainerExecutor_00024Native_00024WinutilsProcessStub_resume( + JNIEnv *env, jobject objSelf) { + + DWORD dwError; + HANDLE hThread = (HANDLE)(*env)->GetLongField(env, objSelf, wps_hThread); + if (-1 == ResumeThread(hThread)) { + dwError = GetLastError(); + LogDebugMessage(L"ResumeThread: %x error:%d\n", hThread, dwError); + throw_ioe(env, dwError); + } +} + +/* + * native int exitValue(); + * + * The "00024" in the function name is an artifact of how JNI encodes + * special characters. U+0024 is '$'. + */ +JNIEXPORT jint JNICALL +Java_org_apache_hadoop_yarn_server_nodemanager_WindowsSecureContainerExecutor_00024Native_00024WinutilsProcessStub_exitValue( + JNIEnv *env, jobject objSelf) { + + DWORD exitCode; + DWORD dwError; + HANDLE hProcess = (HANDLE)(*env)->GetLongField(env, objSelf, wps_hProcess); + if (!GetExitCodeProcess(hProcess, &exitCode)) { + dwError = GetLastError(); + throw_ioe(env, dwError); + return dwError; // exception was thrown, return value doesn't really matter + } + LogDebugMessage(L"GetExitCodeProcess: %x :%d\n", hProcess, exitCode); + + return exitCode; +} + + +/* + * native void dispose(); + * + * The "00024" in the function name is an artifact of how JNI encodes + * special characters. U+0024 is '$'. + */ +JNIEXPORT void JNICALL +Java_org_apache_hadoop_yarn_server_nodemanager_WindowsSecureContainerExecutor_00024Native_00024WinutilsProcessStub_dispose( + JNIEnv *env, jobject objSelf) { + + HANDLE hProcess = INVALID_HANDLE_VALUE, + hThread = INVALID_HANDLE_VALUE; + + jboolean disposed = (*env)->GetBooleanField(env, objSelf, wps_disposed); + + if (JNI_TRUE != disposed) { + hProcess = (HANDLE)(*env)->GetLongField(env, objSelf, wps_hProcess); + hThread = (HANDLE)(*env)->GetLongField(env, objSelf, wps_hThread); + + CloseHandle(hProcess); + CloseHandle(hThread); + (*env)->SetBooleanField(env, objSelf, wps_disposed, JNI_TRUE); + LogDebugMessage(L"disposed: %p\n", objSelf); + } +} + + +/* + * native static FileDescriptor getFileDescriptorFromHandle(long handle); + * + * The "00024" in the function name is an artifact of how JNI encodes + * special characters. U+0024 is '$'. + */ +JNIEXPORT jobject JNICALL +Java_org_apache_hadoop_yarn_server_nodemanager_WindowsSecureContainerExecutor_00024Native_00024WinutilsProcessStub_getFileDescriptorFromHandle( + JNIEnv *env, jclass klass, jlong handle) { + + LogDebugMessage(L"getFileDescriptorFromHandle: %x\n", handle); + return fd_create(env, (long) handle); +} + diff --git a/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/yarn/server/nodemanager/windows_secure_container_executor.h b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/yarn/server/nodemanager/windows_secure_container_executor.h new file mode 100644 index 00000000000..381b42465bc --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/native/src/org/apache/hadoop/yarn/server/nodemanager/windows_secure_container_executor.h @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + + +#define WINUTILS_PROCESS_STUB_CLASS "org/apache/hadoop/yarn/server/nodemanager/WindowsSecureContainerExecutor$Native$WinutilsProcessStub" + +void winutils_process_stub_init(JNIEnv *env); +void winutils_process_stub_deinit(JNIEnv *env); +jobject winutils_process_stub_create(JNIEnv *env, + jlong hProcess, jlong hThread, jlong hStdIn, jlong hStdOut, jlong hStdErr); + + diff --git a/hadoop-common-project/hadoop-common/src/main/winutils/chown.c b/hadoop-common-project/hadoop-common/src/main/winutils/chown.c index 1be81216974..d124f737cb3 100644 --- a/hadoop-common-project/hadoop-common/src/main/winutils/chown.c +++ b/hadoop-common-project/hadoop-common/src/main/winutils/chown.c @@ -17,93 +17,6 @@ #include "winutils.h" -//---------------------------------------------------------------------------- -// Function: ChangeFileOwnerBySid -// -// Description: -// Change a file or directory ownership by giving new owner and group SIDs -// -// Returns: -// ERROR_SUCCESS: on success -// Error code: otherwise -// -// Notes: -// This function is long path safe, i.e. the path will be converted to long -// path format if not already converted. So the caller does not need to do -// the converstion before calling the method. -// -static DWORD ChangeFileOwnerBySid(__in LPCWSTR path, - __in_opt PSID pNewOwnerSid, __in_opt PSID pNewGroupSid) -{ - LPWSTR longPathName = NULL; - INT oldMode = 0; - - SECURITY_INFORMATION securityInformation = 0; - - DWORD dwRtnCode = ERROR_SUCCESS; - - // Convert the path the the long path - // - dwRtnCode = ConvertToLongPath(path, &longPathName); - if (dwRtnCode != ERROR_SUCCESS) - { - goto ChangeFileOwnerByNameEnd; - } - - // Get a pointer to the existing owner information and DACL - // - dwRtnCode = FindFileOwnerAndPermission(longPathName, FALSE, NULL, NULL, &oldMode); - if (dwRtnCode != ERROR_SUCCESS) - { - goto ChangeFileOwnerByNameEnd; - } - - // We need SeTakeOwnershipPrivilege to set the owner if the caller does not - // have WRITE_OWNER access to the object; we need SeRestorePrivilege if the - // SID is not contained in the caller's token, and have the SE_GROUP_OWNER - // permission enabled. - // - if (EnablePrivilege(L"SeTakeOwnershipPrivilege") != ERROR_SUCCESS) - { - fwprintf(stdout, L"INFO: The user does not have SeTakeOwnershipPrivilege.\n"); - } - if (EnablePrivilege(L"SeRestorePrivilege") != ERROR_SUCCESS) - { - fwprintf(stdout, L"INFO: The user does not have SeRestorePrivilege.\n"); - } - - assert(pNewOwnerSid != NULL || pNewGroupSid != NULL); - - // Set the owners of the file. - // - if (pNewOwnerSid != NULL) securityInformation |= OWNER_SECURITY_INFORMATION; - if (pNewGroupSid != NULL) securityInformation |= GROUP_SECURITY_INFORMATION; - dwRtnCode = SetNamedSecurityInfoW( - longPathName, - SE_FILE_OBJECT, - securityInformation, - pNewOwnerSid, - pNewGroupSid, - NULL, - NULL); - if (dwRtnCode != ERROR_SUCCESS) - { - goto ChangeFileOwnerByNameEnd; - } - - // Set the permission on the file for the new owner. - // - dwRtnCode = ChangeFileModeByMask(longPathName, oldMode); - if (dwRtnCode != ERROR_SUCCESS) - { - goto ChangeFileOwnerByNameEnd; - } - -ChangeFileOwnerByNameEnd: - LocalFree(longPathName); - return dwRtnCode; -} - //---------------------------------------------------------------------------- // Function: Chown // @@ -130,9 +43,6 @@ int Chown(__in int argc, __in_ecount(argc) wchar_t *argv[]) LPWSTR groupName = NULL; size_t groupNameLen = 0; - PSID pNewOwnerSid = NULL; - PSID pNewGroupSid = NULL; - DWORD dwRtnCode = 0; int ret = EXIT_FAILURE; @@ -210,48 +120,16 @@ int Chown(__in int argc, __in_ecount(argc) wchar_t *argv[]) goto ChownEnd; } - if (userName != NULL) - { - dwRtnCode = GetSidFromAcctNameW(userName, &pNewOwnerSid); - if (dwRtnCode != ERROR_SUCCESS) - { - ReportErrorCode(L"GetSidFromAcctName", dwRtnCode); - fwprintf(stderr, L"Invalid user name: %s\n", userName); - goto ChownEnd; - } - } - - if (groupName != NULL) - { - dwRtnCode = GetSidFromAcctNameW(groupName, &pNewGroupSid); - if (dwRtnCode != ERROR_SUCCESS) - { - ReportErrorCode(L"GetSidFromAcctName", dwRtnCode); - fwprintf(stderr, L"Invalid group name: %s\n", groupName); - goto ChownEnd; - } - } - - if (wcslen(pathName) == 0 || wcsspn(pathName, L"/?|><:*\"") != 0) - { - fwprintf(stderr, L"Incorrect file name format: %s\n", pathName); - goto ChownEnd; - } - - dwRtnCode = ChangeFileOwnerBySid(pathName, pNewOwnerSid, pNewGroupSid); - if (dwRtnCode != ERROR_SUCCESS) - { - ReportErrorCode(L"ChangeFileOwnerBySid", dwRtnCode); - goto ChownEnd; - } + dwRtnCode = ChownImpl(userName, groupName, pathName); + if (dwRtnCode) { + goto ChownEnd; + } ret = EXIT_SUCCESS; ChownEnd: LocalFree(userName); LocalFree(groupName); - LocalFree(pNewOwnerSid); - LocalFree(pNewGroupSid); return ret; } diff --git a/hadoop-common-project/hadoop-common/src/main/winutils/client.c b/hadoop-common-project/hadoop-common/src/main/winutils/client.c new file mode 100644 index 00000000000..047bfb5440f --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/winutils/client.c @@ -0,0 +1,498 @@ +/** +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with this +* work for additional information regarding copyright ownership. The ASF +* licenses this file to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations under +* the License. +*/ + +#include "winutils.h" +#include +#include +#include "hadoopwinutilsvc_h.h" + +#pragma comment(lib, "Rpcrt4.lib") +#pragma comment(lib, "advapi32.lib") + +static ACCESS_MASK CLIENT_MASK = 1; + +VOID ReportClientError(LPWSTR lpszLocation, DWORD dwError) { + LPWSTR debugMsg = NULL; + int len; + WCHAR hexError[32]; + HRESULT hr; + + if (IsDebuggerPresent()) { + len = FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, dwError, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&debugMsg, 0, NULL); + + LogDebugMessage(L"%s: %s: %x: %.*s\n", GetSystemTimeString(), lpszLocation, dwError, len, debugMsg); + } + + if (NULL != debugMsg) LocalFree(debugMsg); +} + +DWORD PrepareRpcBindingHandle( + __out RPC_BINDING_HANDLE* pHadoopWinutilsSvcBinding) { + DWORD dwError = EXIT_FAILURE; + RPC_STATUS status; + LPWSTR lpszStringBinding = NULL; + ULONG ulCode; + RPC_SECURITY_QOS_V3 qos; + SID_IDENTIFIER_AUTHORITY authNT = SECURITY_NT_AUTHORITY; + BOOL rpcBindingInit = FALSE; + PSID pLocalSystemSid = NULL; + DWORD cbSystemSidSize = SECURITY_MAX_SID_SIZE; + + pLocalSystemSid = (PSID) LocalAlloc(LPTR, cbSystemSidSize); + if (!pLocalSystemSid) { + dwError = GetLastError(); + ReportClientError(L"LocalAlloc", dwError); + goto done; + } + + if (!CreateWellKnownSid(WinLocalSystemSid, NULL, pLocalSystemSid, &cbSystemSidSize)) { + dwError = GetLastError(); + ReportClientError(L"CreateWellKnownSid", dwError); + goto done; + } + + ZeroMemory(&qos, sizeof(qos)); + qos.Version = RPC_C_SECURITY_QOS_VERSION_3; + qos.Capabilities = RPC_C_QOS_CAPABILITIES_LOCAL_MA_HINT | RPC_C_QOS_CAPABILITIES_MUTUAL_AUTH; + qos.IdentityTracking = RPC_C_QOS_IDENTITY_DYNAMIC; + qos.ImpersonationType = RPC_C_IMP_LEVEL_DEFAULT; + qos.Sid = pLocalSystemSid; + + status = RpcStringBindingCompose(NULL, + SVCBINDING, + NULL, + SVCNAME, + NULL, + &lpszStringBinding); + if (RPC_S_OK != status) { + ReportClientError(L"RpcStringBindingCompose", status); + dwError = status; + goto done; + } + + status = RpcBindingFromStringBinding(lpszStringBinding, pHadoopWinutilsSvcBinding); + + if (RPC_S_OK != status) { + ReportClientError(L"RpcBindingFromStringBinding", status); + dwError = status; + goto done; + } + rpcBindingInit = TRUE; + + status = RpcBindingSetAuthInfoEx( + *pHadoopWinutilsSvcBinding, + NULL, + RPC_C_AUTHN_LEVEL_PKT_PRIVACY, // AuthnLevel + RPC_C_AUTHN_WINNT, // AuthnSvc + NULL, // AuthnIdentity (self) + RPC_C_AUTHZ_NONE, // AuthzSvc + &qos); + if (RPC_S_OK != status) { + ReportClientError(L"RpcBindingSetAuthInfoEx", status); + dwError = status; + goto done; + } + + dwError = ERROR_SUCCESS; + +done: + + if (dwError && rpcBindingInit) RpcBindingFree(pHadoopWinutilsSvcBinding); + + if (pLocalSystemSid) LocalFree(pLocalSystemSid); + + if (NULL != lpszStringBinding) { + status = RpcStringFree(&lpszStringBinding); + if (RPC_S_OK != status) { + ReportClientError(L"RpcStringFree", status); + } + } + + return dwError; +} + +DWORD RpcCall_WinutilsKillTask( + __in LPCWSTR taskName) { + + DWORD dwError = EXIT_FAILURE; + ULONG ulCode; + KILLTASK_REQUEST request; + RPC_BINDING_HANDLE hHadoopWinutilsSvcBinding; + BOOL rpcBindingInit = FALSE; + + dwError = PrepareRpcBindingHandle(&hHadoopWinutilsSvcBinding); + if (dwError) { + ReportClientError(L"PrepareRpcBindingHandle", dwError); + goto done; + } + rpcBindingInit = TRUE; + + ZeroMemory(&request, sizeof(request)); + request.taskName = taskName; + + RpcTryExcept { + dwError = WinutilsKillTask(hHadoopWinutilsSvcBinding, &request); + } + RpcExcept(1) { + ulCode = RpcExceptionCode(); + ReportClientError(L"RpcExcept", ulCode); + dwError = (DWORD) ulCode; + } + RpcEndExcept; + +done: + if (rpcBindingInit) RpcBindingFree(&hHadoopWinutilsSvcBinding); + + LogDebugMessage(L"RpcCall_WinutilsKillTask: %s :%d\n", taskName, dwError); + + return dwError; +} + +DWORD RpcCall_WinutilsMkDir( + __in LPCWSTR filePath) { + + DWORD dwError = EXIT_FAILURE; + ULONG ulCode; + MKDIR_REQUEST request; + RPC_BINDING_HANDLE hHadoopWinutilsSvcBinding; + BOOL rpcBindingInit = FALSE; + + dwError = PrepareRpcBindingHandle(&hHadoopWinutilsSvcBinding); + if (dwError) { + ReportClientError(L"PrepareRpcBindingHandle", dwError); + goto done; + } + rpcBindingInit = TRUE; + + ZeroMemory(&request, sizeof(request)); + request.filePath = filePath; + + RpcTryExcept { + dwError = WinutilsMkDir(hHadoopWinutilsSvcBinding, &request); + } + RpcExcept(1) { + ulCode = RpcExceptionCode(); + ReportClientError(L"RpcExcept", ulCode); + dwError = (DWORD) ulCode; + } + RpcEndExcept; + +done: + if (rpcBindingInit) RpcBindingFree(&hHadoopWinutilsSvcBinding); + + LogDebugMessage(L"RpcCall_WinutilsMkDir: %s :%d\n", filePath, dwError); + + return dwError; +} + + + +DWORD RpcCall_WinutilsChown( + __in LPCWSTR filePath, + __in_opt LPCWSTR ownerName, + __in_opt LPCWSTR groupName) { + + DWORD dwError = EXIT_FAILURE; + ULONG ulCode; + CHOWN_REQUEST request; + RPC_BINDING_HANDLE hHadoopWinutilsSvcBinding; + BOOL rpcBindingInit = FALSE; + + dwError = PrepareRpcBindingHandle(&hHadoopWinutilsSvcBinding); + if (dwError) { + ReportClientError(L"PrepareRpcBindingHandle", dwError); + goto done; + } + rpcBindingInit = TRUE; + + ZeroMemory(&request, sizeof(request)); + request.filePath = filePath; + request.ownerName = ownerName; + request.groupName = groupName; + + RpcTryExcept { + dwError = WinutilsChown(hHadoopWinutilsSvcBinding, &request); + } + RpcExcept(1) { + ulCode = RpcExceptionCode(); + ReportClientError(L"RpcExcept", ulCode); + dwError = (DWORD) ulCode; + } + RpcEndExcept; + +done: + if (rpcBindingInit) RpcBindingFree(&hHadoopWinutilsSvcBinding); + + LogDebugMessage(L"RpcCall_WinutilsChown: %s %s %s :%d\n", + ownerName, groupName, filePath, dwError); + + return dwError; +} + + +DWORD RpcCall_WinutilsChmod( + __in LPCWSTR filePath, + __in int mode) { + + DWORD dwError = EXIT_FAILURE; + ULONG ulCode; + CHMOD_REQUEST request; + RPC_BINDING_HANDLE hHadoopWinutilsSvcBinding; + BOOL rpcBindingInit = FALSE; + + dwError = PrepareRpcBindingHandle(&hHadoopWinutilsSvcBinding); + if (dwError) { + ReportClientError(L"PrepareRpcBindingHandle", dwError); + goto done; + } + rpcBindingInit = TRUE; + + ZeroMemory(&request, sizeof(request)); + request.filePath = filePath; + request.mode = mode; + + RpcTryExcept { + dwError = WinutilsChmod(hHadoopWinutilsSvcBinding, &request); + } + RpcExcept(1) { + ulCode = RpcExceptionCode(); + ReportClientError(L"RpcExcept", ulCode); + dwError = (DWORD) ulCode; + } + RpcEndExcept; + +done: + if (rpcBindingInit) RpcBindingFree(&hHadoopWinutilsSvcBinding); + + LogDebugMessage(L"RpcCall_WinutilsChmod: %s %o :%d\n", + filePath, mode, dwError); + + return dwError; +} + + + +DWORD RpcCall_WinutilsMoveFile( + __in int operation, + __in LPCWSTR sourcePath, + __in LPCWSTR destinationPath, + __in BOOL replaceExisting) { + + DWORD dwError = EXIT_FAILURE; + ULONG ulCode; + MOVEFILE_REQUEST request; + RPC_BINDING_HANDLE hHadoopWinutilsSvcBinding; + BOOL rpcBindingInit = FALSE; + + dwError = PrepareRpcBindingHandle(&hHadoopWinutilsSvcBinding); + if (dwError) { + ReportClientError(L"PrepareRpcBindingHandle", dwError); + goto done; + } + rpcBindingInit = TRUE; + + ZeroMemory(&request, sizeof(request)); + request.operation = operation; + request.sourcePath = sourcePath; + request.destinationPath = destinationPath; + request.replaceExisting = replaceExisting; + + RpcTryExcept { + dwError = WinutilsMoveFile(hHadoopWinutilsSvcBinding, &request); + } + RpcExcept(1) { + ulCode = RpcExceptionCode(); + ReportClientError(L"RpcExcept", ulCode); + dwError = (DWORD) ulCode; + } + RpcEndExcept; + +done: + if (rpcBindingInit) RpcBindingFree(&hHadoopWinutilsSvcBinding); + + LogDebugMessage(L"RpcCall_WinutilsMoveFile: %s %s %d :%d\n", + sourcePath, destinationPath, replaceExisting, dwError); + + return dwError; +} + +DWORD RpcCall_WinutilsCreateFile( + __in LPCWSTR path, + __in DWORD desiredAccess, + __in DWORD shareMode, + __in DWORD creationDisposition, + __in DWORD flags, + __out HANDLE* hFile) { + + DWORD dwError = EXIT_FAILURE; + ULONG ulCode; + DWORD dwSelfPid = GetCurrentProcessId(); + CREATEFILE_REQUEST request; + CREATEFILE_RESPONSE *response = NULL; + RPC_BINDING_HANDLE hHadoopWinutilsSvcBinding; + BOOL rpcBindingInit = FALSE; + + dwError = PrepareRpcBindingHandle(&hHadoopWinutilsSvcBinding); + if (dwError) { + ReportClientError(L"PrepareRpcBindingHandle", dwError); + goto done; + } + rpcBindingInit = TRUE; + + ZeroMemory(&request, sizeof(request)); + request.path = path; + request.desiredAccess = desiredAccess; + request.shareMode = shareMode; + request.creationDisposition = creationDisposition; + request.flags = flags; + + RpcTryExcept { + dwError = WinutilsCreateFile(hHadoopWinutilsSvcBinding, dwSelfPid, &request, &response); + } + RpcExcept(1) { + ulCode = RpcExceptionCode(); + ReportClientError(L"RpcExcept", ulCode); + dwError = (DWORD) ulCode; + } + RpcEndExcept; + + if (ERROR_SUCCESS == dwError) { + *hFile = response->hFile; + } + +done: + if (rpcBindingInit) RpcBindingFree(&hHadoopWinutilsSvcBinding); + + if(NULL != response) MIDL_user_free(response); + + LogDebugMessage(L"RpcCall_WinutilsCreateFile: %s %d, %d, %d, %d :%d\n", + path, desiredAccess, shareMode, creationDisposition, flags, dwError); + + return dwError; +} + + +DWORD RpcCall_WinutilsDeletePath( + __in LPCWSTR path, + __in BOOL isDir, + __out BOOL* pDeleted) { + + DWORD dwError = EXIT_FAILURE; + ULONG ulCode; + DELETEPATH_REQUEST request; + DELETEPATH_RESPONSE *response = NULL; + RPC_BINDING_HANDLE hHadoopWinutilsSvcBinding; + BOOL rpcBindingInit = FALSE; + + pDeleted = FALSE; + + dwError = PrepareRpcBindingHandle(&hHadoopWinutilsSvcBinding); + if (dwError) { + ReportClientError(L"PrepareRpcBindingHandle", dwError); + goto done; + } + rpcBindingInit = TRUE; + + ZeroMemory(&request, sizeof(request)); + request.path = path; + request.type = isDir ? PATH_IS_DIR : PATH_IS_FILE; + + RpcTryExcept { + dwError = WinutilsDeletePath(hHadoopWinutilsSvcBinding, &request, &response); + } + RpcExcept(1) { + ulCode = RpcExceptionCode(); + ReportClientError(L"RpcExcept", ulCode); + dwError = (DWORD) ulCode; + } + RpcEndExcept; + + if (ERROR_SUCCESS == dwError) { + *pDeleted = response->deleted; + } + +done: + if (rpcBindingInit) RpcBindingFree(&hHadoopWinutilsSvcBinding); + + if(NULL != response) MIDL_user_free(response); + + LogDebugMessage(L"RpcCall_WinutilsDeletePath: %s %d: %d %d\n", + path, isDir, *pDeleted, dwError); + + return dwError; +} + + +DWORD RpcCall_TaskCreateAsUser( + LPCWSTR cwd, LPCWSTR jobName, + LPCWSTR user, LPCWSTR pidFile, LPCWSTR cmdLine, + HANDLE* phProcess, HANDLE* phThread, HANDLE* phStdIn, HANDLE* phStdOut, HANDLE* phStdErr) +{ + DWORD dwError = EXIT_FAILURE; + ULONG ulCode; + DWORD dwSelfPid = GetCurrentProcessId(); + CREATE_PROCESS_REQUEST request; + CREATE_PROCESS_RESPONSE *response = NULL; + RPC_BINDING_HANDLE hHadoopWinutilsSvcBinding; + BOOL rpcBindingInit = FALSE; + + dwError = PrepareRpcBindingHandle(&hHadoopWinutilsSvcBinding); + if (dwError) { + ReportClientError(L"PrepareRpcBindingHandle", dwError); + goto done; + } + rpcBindingInit = TRUE; + + ZeroMemory(&request, sizeof(request)); + request.cwd = cwd; + request.jobName = jobName; + request.user = user; + request.pidFile = pidFile; + request.cmdLine = cmdLine; + + RpcTryExcept { + dwError = WinutilsCreateProcessAsUser(hHadoopWinutilsSvcBinding, dwSelfPid, &request, &response); + } + RpcExcept(1) { + ulCode = RpcExceptionCode(); + ReportClientError(L"RpcExcept", ulCode); + dwError = (DWORD) ulCode; + } + RpcEndExcept; + + if (ERROR_SUCCESS == dwError) { + *phProcess = response->hProcess; + *phThread = response->hThread; + *phStdIn = response->hStdIn; + *phStdOut = response->hStdOut; + *phStdErr = response->hStdErr; + } + +done: + if (rpcBindingInit) RpcBindingFree(&hHadoopWinutilsSvcBinding); + + if (NULL != response) { + MIDL_user_free(response); + } + + return dwError; +} + diff --git a/hadoop-common-project/hadoop-common/src/main/winutils/config.cpp b/hadoop-common-project/hadoop-common/src/main/winutils/config.cpp new file mode 100644 index 00000000000..1e07b7fe342 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/winutils/config.cpp @@ -0,0 +1,174 @@ +/** +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with this +* work for additional information regarding copyright ownership. The ASF +* licenses this file to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations under +* the License. +*/ + +#include "winutils.h" +#include +#include +#import "msxml6.dll" + +#define ERROR_CHECK_HRESULT_DONE(hr, message) \ + if (FAILED(hr)) { \ + dwError = (DWORD) hr; \ + LogDebugMessage(L"%s: %x", message, hr); \ + goto done; \ + } + +DWORD BuildPathRelativeToModule( + __in LPCWSTR relativePath, + __in size_t len, + __out_ecount(len) LPWSTR buffer) { + DWORD dwError = ERROR_SUCCESS; + WCHAR moduleFile[MAX_PATH]; + WCHAR modulePath[_MAX_DIR]; + WCHAR moduleDrive[_MAX_DRIVE]; + DWORD size; + HRESULT hr = S_OK; + errno_t errno; + + size = GetModuleFileName(NULL, moduleFile, MAX_PATH); + dwError = GetLastError(); // Always check due to ERROR_INSUFFICIENT_BUFFER + if (dwError) { + LogDebugMessage(L"GetModuleFileName: %x\n", dwError); + goto done; + } + + errno = _wsplitpath_s(moduleFile, + moduleDrive, _MAX_DRIVE, + modulePath, _MAX_DIR, + NULL, 0, // fname, not interesting + NULL, 0); // extenssion, not interesting + if (errno) { + LogDebugMessage(L"_wsplitpath_s: %x\n", errno); + dwError = ERROR_BAD_PATHNAME; + goto done; + } + + hr = StringCbPrintf(buffer, len, L"%s%s%s", moduleDrive, modulePath, relativePath); + if (FAILED(hr)) { + // There is no reliable HRESULT to WIN32 mapping, use code. + // see http://blogs.msdn.com/b/oldnewthing/archive/2006/11/03/942851.aspx + // + dwError = HRESULT_CODE(hr); + goto done; + } + + LogDebugMessage(L"BuildPathRelativeToModule: %s (%s)\n", buffer, relativePath); + +done: + return dwError; +} + +DWORD GetConfigValue( + __in LPCWSTR relativePath, + __in LPCWSTR keyName, + __out size_t* len, __out_ecount(len) LPCWSTR* value) { + + DWORD dwError = ERROR_SUCCESS; + WCHAR xmlPath[MAX_PATH]; + + *len = 0; + *value = NULL; + + dwError = BuildPathRelativeToModule( + relativePath, + sizeof(xmlPath)/sizeof(WCHAR), + xmlPath); + + if (dwError) { + goto done; + } + + dwError = GetConfigValueFromXmlFile(xmlPath, keyName, len, value); + +done: + if (*len) { + LogDebugMessage(L"GetConfigValue:%d key:%s len:%d value:%.*s from:%s\n", dwError, keyName, *len, *len, *value, xmlPath); + } + return dwError; +} + + +DWORD GetConfigValueFromXmlFile(__in LPCWSTR xmlFile, __in LPCWSTR keyName, + __out size_t* outLen, __out_ecount(len) LPCWSTR* outValue) { + + DWORD dwError = ERROR_SUCCESS; + HRESULT hr; + WCHAR keyXsl[8192]; + size_t len = 0; + LPWSTR value = NULL; + BOOL comInitialized = FALSE; + + *outLen = 0; + *outValue = NULL; + + hr = CoInitialize(NULL); + ERROR_CHECK_HRESULT_DONE(hr, L"CoInitialize"); + comInitialized = TRUE; + + hr = StringCbPrintf(keyXsl, sizeof(keyXsl), L"//configuration/property[name='%s']/value/text()", keyName); + ERROR_CHECK_HRESULT_DONE(hr, L"StringCbPrintf"); + + try { + MSXML2::IXMLDOMDocument2Ptr pDoc; + hr = pDoc.CreateInstance(__uuidof(MSXML2::DOMDocument60), NULL, CLSCTX_INPROC_SERVER); + ERROR_CHECK_HRESULT_DONE(hr, L"CreateInstance"); + + pDoc->async = VARIANT_FALSE; + pDoc->validateOnParse = VARIANT_FALSE; + pDoc->resolveExternals = VARIANT_FALSE; + + _variant_t file(xmlFile); + + if (VARIANT_FALSE == pDoc->load(file)) { + dwError = pDoc->parseError->errorCode; + LogDebugMessage(L"load %s failed:%d %s\n", xmlFile, dwError, + static_cast(pDoc->parseError->Getreason())); + goto done; + } + + MSXML2::IXMLDOMElementPtr pRoot = pDoc->documentElement; + MSXML2::IXMLDOMNodePtr keyNode = pRoot->selectSingleNode(keyXsl); + + if (keyNode) { + _bstr_t bstrValue = static_cast<_bstr_t>(keyNode->nodeValue); + len = bstrValue.length(); + value = (LPWSTR) LocalAlloc(LPTR, (len+1) * sizeof(WCHAR)); + LPCWSTR lpwszValue = static_cast(bstrValue); + memcpy(value, lpwszValue, (len) * sizeof(WCHAR)); + LogDebugMessage(L"key:%s :%.*s [%s]\n", keyName, len, value, lpwszValue); + *outLen = len; + *outValue = value; + } + else { + LogDebugMessage(L"node Xpath:%s not found in:%s\n", keyXsl, xmlFile); + } + } + catch(_com_error errorObject) { + dwError = errorObject.Error(); + LogDebugMessage(L"catch _com_error:%x %s\n", dwError, errorObject.ErrorMessage()); + goto done; + } + +done: + if (comInitialized) { + CoUninitialize(); + } + + return dwError; +} + + diff --git a/hadoop-common-project/hadoop-common/src/main/winutils/hadoopwinutilsvc.idl b/hadoop-common-project/hadoop-common/src/main/winutils/hadoopwinutilsvc.idl new file mode 100644 index 00000000000..ee3685c075a --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/winutils/hadoopwinutilsvc.idl @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import "oaidl.idl"; +import "ocidl.idl"; + +[ + uuid(0492311C-1718-4F53-A6EB-86AD7039988D), + version(1.0), + pointer_default(unique), + //implicit_handle(handle_t hHadoopWinutilsSvcBinding), + endpoint("ncalrpc:[hadoopwinutilsvc]"), +#ifndef __midl + explicit_handle +#endif +] +interface HadoopWinutilSvc +{ + typedef struct { + [string] const wchar_t* cwd; + [string] const wchar_t* jobName; + [string] const wchar_t* user; + [string] const wchar_t* pidFile; + [string] const wchar_t* cmdLine; + } CREATE_PROCESS_REQUEST; + + typedef struct { + LONG_PTR hProcess; + LONG_PTR hThread; + LONG_PTR hStdIn; + LONG_PTR hStdOut; + LONG_PTR hStdErr; + } CREATE_PROCESS_RESPONSE; + + typedef struct { + [string] const wchar_t* filePath; + [string] const wchar_t* ownerName; + [string] const wchar_t* groupName; + } CHOWN_REQUEST; + + typedef struct { + [string] const wchar_t* filePath; + int mode; + } CHMOD_REQUEST; + + typedef struct { + [string] const wchar_t* filePath; + } MKDIR_REQUEST; + + typedef enum { MOVE_FILE = 1, COPY_FILE = 2} MOVE_COPY_OPERATION; + + typedef struct { + MOVE_COPY_OPERATION operation; + [string] const wchar_t* sourcePath; + [string] const wchar_t* destinationPath; + boolean replaceExisting; + } MOVEFILE_REQUEST; + + typedef struct { + [string] const wchar_t* path; + int desiredAccess; + int shareMode; + int creationDisposition; + int flags; + } CREATEFILE_REQUEST; + + typedef struct { + LONG_PTR hFile; + } CREATEFILE_RESPONSE; + + typedef enum {PATH_IS_DIR = 1, PATH_IS_FILE = 2} DELETEPATH_TYPE; + + typedef struct { + DELETEPATH_TYPE type; + [string] const wchar_t* path; + } DELETEPATH_REQUEST; + + typedef struct { + boolean deleted; + } DELETEPATH_RESPONSE; + + typedef struct { + [string] const wchar_t* taskName; + } KILLTASK_REQUEST; + + error_status_t WinutilsKillTask( + [in] KILLTASK_REQUEST *request); + + error_status_t WinutilsMkDir( + [in] MKDIR_REQUEST *request); + + error_status_t WinutilsMoveFile( + [in] MOVEFILE_REQUEST *request); + + error_status_t WinutilsChown( + [in] CHOWN_REQUEST *request); + + error_status_t WinutilsChmod( + [in] CHMOD_REQUEST *request); + + error_status_t WinutilsCreateFile( + [in] int nmPid, + [in] CREATEFILE_REQUEST *request, + [out] CREATEFILE_RESPONSE **response); + + error_status_t WinutilsDeletePath( + [in] DELETEPATH_REQUEST *request, + [out] DELETEPATH_RESPONSE **response); + + error_status_t WinutilsCreateProcessAsUser( + [in] int nmPid, + [in] CREATE_PROCESS_REQUEST *request, + [out] CREATE_PROCESS_RESPONSE **response); + +} diff --git a/hadoop-common-project/hadoop-common/src/main/winutils/include/winutils.h b/hadoop-common-project/hadoop-common/src/main/winutils/include/winutils.h index bae754c9b6e..77fc58637ff 100644 --- a/hadoop-common-project/hadoop-common/src/main/winutils/include/winutils.h +++ b/hadoop-common-project/hadoop-common/src/main/winutils/include/winutils.h @@ -30,6 +30,11 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + + enum EXIT_CODE { /* Common success exit code shared among all utilities */ @@ -38,6 +43,12 @@ enum EXIT_CODE FAILURE = EXIT_FAILURE, /* Failure code indicates the user does not privilege to create symlinks */ SYMLINK_NO_PRIVILEGE = 2, + + ERROR_TASK_NOT_ALIVE = 1, + + // This exit code for killed processes is compatible with Unix, where a killed + // process exits with 128 + signal. For SIGKILL, this would be 128 + 9 = 137. + KILLED_PROCESS_EXIT_CODE = 137, }; @@ -101,6 +112,8 @@ void GroupsUsage(LPCWSTR program); int Hardlink(__in int argc, __in_ecount(argc) wchar_t *argv[]); void HardlinkUsage(); +DWORD KillTask(PCWSTR jobObjName); + int Task(__in int argc, __in_ecount(argc) wchar_t *argv[]); void TaskUsage(); @@ -167,7 +180,7 @@ void UnregisterWithLsa(__in HANDLE lsaHandle); DWORD LookupKerberosAuthenticationPackageId(__in HANDLE lsaHandle, __out ULONG * packageId); -DWORD CreateLogonForUser(__in HANDLE lsaHandle, +DWORD CreateLogonTokenForUser(__in HANDLE lsaHandle, __in const char * tokenSourceName, __in const char * tokenOriginName, __in ULONG authnPkgId, @@ -178,3 +191,102 @@ DWORD LoadUserProfileForLogon(__in HANDLE logonHandle, __out PROFILEINFO * pi); DWORD UnloadProfileForLogon(__in HANDLE logonHandle, __in PROFILEINFO * pi); +DWORD EnableImpersonatePrivileges(); + +DWORD RunService(__in int argc, __in_ecount(argc) wchar_t *argv[]); +void ServiceUsage(); + + +DWORD ChangeFileOwnerBySid(__in LPCWSTR path, + __in_opt PSID pNewOwnerSid, __in_opt PSID pNewGroupSid); + +DWORD ChownImpl( + __in_opt LPCWSTR userName, + __in_opt LPCWSTR groupName, + __in LPCWSTR pathName); + +LPCWSTR GetSystemTimeString(); + +VOID LogDebugMessage(LPCWSTR format, ...); + +DWORD SplitStringIgnoreSpaceW( + __in size_t len, + __in_ecount(len) LPCWSTR source, + __in WCHAR deli, + __out size_t* count, __out_ecount(count) WCHAR*** out); + +DWORD BuildPathRelativeToModule( + __in LPCWSTR relativePath, + __in size_t len, + __out_ecount(len) LPWSTR buffer); + +DWORD GetConfigValue( + __in LPCWSTR relativePath, + __in LPCWSTR keyName, + __out size_t* len, + __out_ecount(len) LPCWSTR* value); +DWORD GetConfigValueFromXmlFile( + __in LPCWSTR xmlFile, + __in LPCWSTR keyName, + __out size_t* len, + __out_ecount(len) LPCWSTR* value); + + +DWORD BuildServiceSecurityDescriptor( + __in ACCESS_MASK accessMask, + __in size_t grantSidCount, + __in_ecount(grantSidCount) PSID* pGrantSids, + __in size_t denySidCount, + __in_ecount(denySidCount) PSID* pDenySids, + __in_opt PSID pOwner, + __out PSECURITY_DESCRIPTOR* pSD); + +DWORD AddNodeManagerAndUserACEsToObject( + __in HANDLE hProcess, + __in LPWSTR user); + + +DWORD GetSecureJobObjectName( + __in LPCWSTR jobName, + __in size_t cchSecureJobName, + __out_ecount(cchSecureJobName) LPWSTR secureJobName); + +extern const WCHAR* wsceConfigRelativePath; + +extern LPCWSTR NM_WSCE_ALLOWED; + + +#define SVCNAME TEXT("hadoopwinutilsvc") +#define SVCBINDING TEXT("ncalrpc") + +DWORD RpcCall_WinutilsKillTask( + __in LPCWSTR taskName); + +DWORD RpcCall_TaskCreateAsUser( + LPCWSTR cwd, LPCWSTR jobName, + LPCWSTR user, LPCWSTR pidFile, LPCWSTR cmdLine, + HANDLE* phProcess, HANDLE* phThread, HANDLE* phStdIn, HANDLE* phStdOut, HANDLE* phStdErr); + +DWORD RpcCall_WinutilsCreateFile( + __in LPCWSTR path, + __in DWORD desiredAccess, + __in DWORD shareMode, + __in DWORD creationDisposition, + __in DWORD flags, + __out HANDLE* hFile); + +DWORD RpcCall_WinutilsMoveFile( + __in LPCWSTR sourcePath, + __in LPCWSTR destinationPath, + __in BOOL replaceExisting); + +DWORD RpcCall_WinutilsDeletePath( + __in LPCWSTR path, + __in BOOL isDir, + __out BOOL* pDeleted); + +#ifdef __cplusplus +} +#endif + + diff --git a/hadoop-common-project/hadoop-common/src/main/winutils/libwinutils.c b/hadoop-common-project/hadoop-common/src/main/winutils/libwinutils.c index f27511173da..933c177b463 100644 --- a/hadoop-common-project/hadoop-common/src/main/winutils/libwinutils.c +++ b/hadoop-common-project/hadoop-common/src/main/winutils/libwinutils.c @@ -19,9 +19,24 @@ #pragma comment(lib, "netapi32.lib") #pragma comment(lib, "Secur32.lib") #pragma comment(lib, "Userenv.lib") +#pragma comment(lib, "Ntdsapi.lib") + #include "winutils.h" +#include +#include #include #include +#include +#include + +#define WIDEN_STRING(x) WIDEN_STRING_(x) +#define WIDEN_STRING_(x) L ## x +#define STRINGIFY(x) STRINGIFY_(x) +#define STRINGIFY_(x) #x + + +#pragma message("WSCE config is " STRINGIFY(WSCE_CONFIG_DIR) "\\" STRINGIFY(WSCE_CONFIG_FILE)) +const WCHAR* wsceConfigRelativePath = WIDEN_STRING(STRINGIFY(WSCE_CONFIG_DIR)) L"\\" WIDEN_STRING(STRINGIFY(WSCE_CONFIG_FILE)); /* * The array of 12 months' three-letter abbreviations @@ -237,10 +252,10 @@ ConvertToLongPathExit: // Function: IsDirFileInfo // // Description: -// Test if the given file information is a directory +// Test if the given file information is a directory // // Returns: -// TRUE if it is a directory +// TRUE if it is a directory // FALSE otherwise // // Notes: @@ -257,10 +272,10 @@ BOOL IsDirFileInfo(const BY_HANDLE_FILE_INFORMATION *fileInformation) // Function: CheckFileAttributes // // Description: -// Check if the given file has all the given attribute(s) +// Check if the given file has all the given attribute(s) // // Returns: -// ERROR_SUCCESS on success +// ERROR_SUCCESS on success // error code otherwise // // Notes: @@ -281,10 +296,10 @@ static DWORD FileAttributesCheck( // Function: IsDirectory // // Description: -// Check if the given file is a directory +// Check if the given file is a directory // // Returns: -// ERROR_SUCCESS on success +// ERROR_SUCCESS on success // error code otherwise // // Notes: @@ -298,10 +313,10 @@ DWORD DirectoryCheck(__in LPCWSTR pathName, __out PBOOL res) // Function: IsReparsePoint // // Description: -// Check if the given file is a reparse point +// Check if the given file is a reparse point // // Returns: -// ERROR_SUCCESS on success +// ERROR_SUCCESS on success // error code otherwise // // Notes: @@ -315,10 +330,10 @@ static DWORD ReparsePointCheck(__in LPCWSTR pathName, __out PBOOL res) // Function: CheckReparseTag // // Description: -// Check if the given file is a reparse point of the given tag. +// Check if the given file is a reparse point of the given tag. // // Returns: -// ERROR_SUCCESS on success +// ERROR_SUCCESS on success // error code otherwise // // Notes: @@ -356,10 +371,10 @@ static DWORD ReparseTagCheck(__in LPCWSTR path, __in DWORD tag, __out PBOOL res) // Function: IsSymbolicLink // // Description: -// Check if the given file is a symbolic link. +// Check if the given file is a symbolic link. // // Returns: -// ERROR_SUCCESS on success +// ERROR_SUCCESS on success // error code otherwise // // Notes: @@ -373,10 +388,10 @@ DWORD SymbolicLinkCheck(__in LPCWSTR pathName, __out PBOOL res) // Function: IsJunctionPoint // // Description: -// Check if the given file is a junction point. +// Check if the given file is a junction point. // // Returns: -// ERROR_SUCCESS on success +// ERROR_SUCCESS on success // error code otherwise // // Notes: @@ -390,14 +405,14 @@ DWORD JunctionPointCheck(__in LPCWSTR pathName, __out PBOOL res) // Function: GetSidFromAcctNameW // // Description: -// To retrieve the SID for a user account +// To retrieve the SID for a user account // // Returns: -// ERROR_SUCCESS: on success +// ERROR_SUCCESS: on success // Other error code: otherwise // // Notes: -// Caller needs to destroy the memory of Sid by calling LocalFree() +// Caller needs to destroy the memory of Sid by calling LocalFree() // DWORD GetSidFromAcctNameW(__in PCWSTR acctName, __out PSID *ppSid) { @@ -479,10 +494,10 @@ DWORD GetSidFromAcctNameW(__in PCWSTR acctName, __out PSID *ppSid) // Function: GetUnixAccessMask // // Description: -// Compute the 3 bit Unix mask for the owner, group, or, others +// Compute the 3 bit Unix mask for the owner, group, or, others // // Returns: -// The 3 bit Unix mask in INT +// The 3 bit Unix mask in INT // // Notes: // @@ -506,10 +521,10 @@ static INT GetUnixAccessMask(ACCESS_MASK Mask) // Function: GetAccess // // Description: -// Get Windows acces mask by AuthZ methods +// Get Windows acces mask by AuthZ methods // // Returns: -// ERROR_SUCCESS: on success +// ERROR_SUCCESS: on success // // Notes: // @@ -554,10 +569,10 @@ static DWORD GetAccess(AUTHZ_CLIENT_CONTEXT_HANDLE hAuthzClient, // Function: GetEffectiveRightsForSid // // Description: -// Get Windows acces mask by AuthZ methods +// Get Windows acces mask by AuthZ methods // // Returns: -// ERROR_SUCCESS: on success +// ERROR_SUCCESS: on success // // Notes: // We run into problems for local user accounts when using the method @@ -714,11 +729,11 @@ CheckAccessEnd: // Function: FindFileOwnerAndPermissionByHandle // // Description: -// Find the owner, primary group and permissions of a file object given the +// Find the owner, primary group and permissions of a file object given the // the file object handle. The function will always follow symbolic links. // // Returns: -// ERROR_SUCCESS: on success +// ERROR_SUCCESS: on success // Error code otherwise // // Notes: @@ -778,10 +793,10 @@ FindFileOwnerAndPermissionByHandleEnd: // Function: FindFileOwnerAndPermission // // Description: -// Find the owner, primary group and permissions of a file object +// Find the owner, primary group and permissions of a file object // // Returns: -// ERROR_SUCCESS: on success +// ERROR_SUCCESS: on success // Error code otherwise // // Notes: @@ -1207,14 +1222,14 @@ static DWORD GetWindowsDACLs(__in INT unixMask, if (winUserAccessDenyMask && !AddAccessDeniedAceEx(pNewDACL, ACL_REVISION, - NO_PROPAGATE_INHERIT_ACE, + CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, winUserAccessDenyMask, pOwnerSid)) { ret = GetLastError(); goto GetWindowsDACLsEnd; } if (!AddAccessAllowedAceEx(pNewDACL, ACL_REVISION, - NO_PROPAGATE_INHERIT_ACE, + CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, winUserAccessAllowMask, pOwnerSid)) { ret = GetLastError(); @@ -1222,21 +1237,21 @@ static DWORD GetWindowsDACLs(__in INT unixMask, } if (winGroupAccessDenyMask && !AddAccessDeniedAceEx(pNewDACL, ACL_REVISION, - NO_PROPAGATE_INHERIT_ACE, + CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, winGroupAccessDenyMask, pGroupSid)) { ret = GetLastError(); goto GetWindowsDACLsEnd; } if (!AddAccessAllowedAceEx(pNewDACL, ACL_REVISION, - NO_PROPAGATE_INHERIT_ACE, + CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, winGroupAccessAllowMask, pGroupSid)) { ret = GetLastError(); goto GetWindowsDACLsEnd; } if (!AddAccessAllowedAceEx(pNewDACL, ACL_REVISION, - NO_PROPAGATE_INHERIT_ACE, + CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, winOtherAccessAllowMask, pEveryoneSid)) { ret = GetLastError(); @@ -1440,14 +1455,14 @@ ChangeFileModeByMaskEnd: // Function: GetAccntNameFromSid // // Description: -// To retrieve an account name given the SID +// To retrieve an account name given the SID // // Returns: -// ERROR_SUCCESS: on success +// ERROR_SUCCESS: on success // Other error code: otherwise // // Notes: -// Caller needs to destroy the memory of account name by calling LocalFree() +// Caller needs to destroy the memory of account name by calling LocalFree() // DWORD GetAccntNameFromSid(__in PSID pSid, __out PWSTR *ppAcctName) { @@ -1536,10 +1551,10 @@ GetAccntNameFromSidEnd: // Function: GetLocalGroupsForUser // // Description: -// Get an array of groups for the given user. +// Get an array of groups for the given user. // // Returns: -// ERROR_SUCCESS on success +// ERROR_SUCCESS on success // Other error code on failure // // Notes: @@ -1631,11 +1646,12 @@ GetLocalGroupsForUserEnd: return ret; } + //---------------------------------------------------------------------------- // Function: EnablePrivilege // // Description: -// Check if the process has the given privilege. If yes, enable the privilege +// Check if the process has the given privilege. If yes, enable the privilege // to the process's access token. // // Returns: @@ -1672,8 +1688,8 @@ DWORD EnablePrivilege(__in LPCWSTR privilegeName) // As stated on MSDN, we need to use GetLastError() to check if // AdjustTokenPrivileges() adjusted all of the specified privileges. // - if( !AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL) ) { - dwErrCode = GetLastError(); + if (!AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL)) { + dwErrCode = GetLastError(); } CloseHandle(hToken); @@ -1706,12 +1722,15 @@ void ReportErrorCode(LPCWSTR func, DWORD err) (LPWSTR)&msg, 0, NULL); if (len > 0) { + LogDebugMessage(L"%s error (%d): %s\n", func, err, msg); fwprintf(stderr, L"%s error (%d): %s\n", func, err, msg); } else { + LogDebugMessage(L"%s error code: %d.\n", func, err); fwprintf(stderr, L"%s error code: %d.\n", func, err); } + if (msg != NULL) LocalFree(msg); } @@ -1843,7 +1862,7 @@ DWORD LookupKerberosAuthenticationPackageId(__in HANDLE lsaHandle, __out ULONG * } //---------------------------------------------------------------------------- -// Function: CreateLogonForUser +// Function: CreateLogonTokenForUser // // Description: // Contacts the local LSA and performs a logon without credential for the @@ -1858,7 +1877,7 @@ DWORD LookupKerberosAuthenticationPackageId(__in HANDLE lsaHandle, __out ULONG * // This call assumes that all required privileges have already been enabled (TCB etc). // IMPORTANT **** tokenOriginName must be immutable! // -DWORD CreateLogonForUser(__in HANDLE lsaHandle, +DWORD CreateLogonTokenForUser(__in HANDLE lsaHandle, __in const char * tokenSourceName, __in const char * tokenOriginName, // must be immutable, will not be copied! __in ULONG authnPkgId, @@ -2026,6 +2045,8 @@ done: return loadProfileStatus; } + + DWORD UnloadProfileForLogon(__in HANDLE logonHandle, __in PROFILEINFO * pi) { DWORD touchProfileStatus = ERROR_ASSERTION_FAILURE; // Failure to set status should trigger error @@ -2046,3 +2067,646 @@ DWORD UnloadProfileForLogon(__in HANDLE logonHandle, __in PROFILEINFO * pi) done: return touchProfileStatus; } + + +//---------------------------------------------------------------------------- +// Function: ChangeFileOwnerBySid +// +// Description: +// Change a file or directory ownership by giving new owner and group SIDs +// +// Returns: +// ERROR_SUCCESS: on success +// Error code: otherwise +// +// Notes: +// This function is long path safe, i.e. the path will be converted to long +// path format if not already converted. So the caller does not need to do +// the converstion before calling the method. +// +DWORD ChangeFileOwnerBySid(__in LPCWSTR path, + __in_opt PSID pNewOwnerSid, __in_opt PSID pNewGroupSid) +{ + LPWSTR longPathName = NULL; + INT oldMode = 0; + + SECURITY_INFORMATION securityInformation = 0; + + DWORD dwRtnCode = ERROR_SUCCESS; + + // Convert the path the the long path + // + dwRtnCode = ConvertToLongPath(path, &longPathName); + if (dwRtnCode != ERROR_SUCCESS) + { + goto ChangeFileOwnerByNameEnd; + } + + // Get a pointer to the existing owner information and DACL + // + dwRtnCode = FindFileOwnerAndPermission(longPathName, FALSE, NULL, NULL, &oldMode); + if (dwRtnCode != ERROR_SUCCESS) + { + goto ChangeFileOwnerByNameEnd; + } + + // We need SeTakeOwnershipPrivilege to set the owner if the caller does not + // have WRITE_OWNER access to the object; we need SeRestorePrivilege if the + // SID is not contained in the caller's token, and have the SE_GROUP_OWNER + // permission enabled. + // + if (EnablePrivilege(L"SeTakeOwnershipPrivilege") != ERROR_SUCCESS) + { + fwprintf(stdout, L"INFO: The user does not have SeTakeOwnershipPrivilege.\n"); + } + if (EnablePrivilege(L"SeRestorePrivilege") != ERROR_SUCCESS) + { + fwprintf(stdout, L"INFO: The user does not have SeRestorePrivilege.\n"); + } + + assert(pNewOwnerSid != NULL || pNewGroupSid != NULL); + + // Set the owners of the file. + // + if (pNewOwnerSid != NULL) securityInformation |= OWNER_SECURITY_INFORMATION; + if (pNewGroupSid != NULL) securityInformation |= GROUP_SECURITY_INFORMATION; + dwRtnCode = SetNamedSecurityInfoW( + longPathName, + SE_FILE_OBJECT, + securityInformation, + pNewOwnerSid, + pNewGroupSid, + NULL, + NULL); + if (dwRtnCode != ERROR_SUCCESS) + { + goto ChangeFileOwnerByNameEnd; + } + + // Set the permission on the file for the new owner. + // + dwRtnCode = ChangeFileModeByMask(longPathName, oldMode); + if (dwRtnCode != ERROR_SUCCESS) + { + goto ChangeFileOwnerByNameEnd; + } + +ChangeFileOwnerByNameEnd: + LocalFree(longPathName); + return dwRtnCode; +} + + +//----------------------------------------------------------------------------- +// Function: GetSecureJobObjectName +// +// Description: +// Creates a job object name usable in a secure environment: adds the Golbal\ +// + +DWORD GetSecureJobObjectName( + __in LPCWSTR jobName, + __in size_t cchSecureJobName, + __out_ecount(cchSecureJobName) LPWSTR secureJobName) { + + HRESULT hr = StringCchPrintf(secureJobName, cchSecureJobName, + L"Global\\%s", jobName); + + if (FAILED(hr)) { + return HRESULT_CODE(hr); + } + + return ERROR_SUCCESS; +} + +//----------------------------------------------------------------------------- +// Function: EnableImpersonatePrivileges +// +// Description: +// Enables the required privileges for S4U impersonation +// +// Returns: +// ERROR_SUCCESS: On success +// +DWORD EnableImpersonatePrivileges() { + DWORD dwError = ERROR_SUCCESS; + LPCWSTR privilege = NULL; + int crt = 0; + + LPCWSTR privileges[] = { + SE_IMPERSONATE_NAME, + SE_TCB_NAME, + SE_ASSIGNPRIMARYTOKEN_NAME, + SE_INCREASE_QUOTA_NAME, + SE_RESTORE_NAME, + SE_DEBUG_NAME, + SE_SECURITY_NAME, + }; + + for (crt = 0; crt < sizeof(privileges)/sizeof(LPCWSTR); ++crt) { + LPCWSTR privilege = privileges[crt]; + dwError = EnablePrivilege(privilege); + if( dwError != ERROR_SUCCESS ) { + LogDebugMessage(L"Failed to enable privilege: %s\n", privilege); + ReportErrorCode(L"EnablePrivilege", dwError); + goto done; + } + } + +done: + return dwError; +} + + +//----------------------------------------------------------------------------- +// Function: KillTask +// +// Description: +// Kills a task via a jobobject. Outputs the +// appropriate information to stdout on success, or stderr on failure. +// +// Returns: +// ERROR_SUCCESS: On success +// GetLastError: otherwise +DWORD KillTask(PCWSTR jobObjName) +{ + DWORD dwError = ERROR_SUCCESS; + + HANDLE jobObject = OpenJobObject(JOB_OBJECT_TERMINATE, FALSE, jobObjName); + if(jobObject == NULL) + { + dwError = GetLastError(); + if(dwError == ERROR_FILE_NOT_FOUND) + { + // job object does not exist. assume its not alive + dwError = ERROR_SUCCESS; + } + goto done; + } + + if(TerminateJobObject(jobObject, KILLED_PROCESS_EXIT_CODE) == 0) + { + dwError = GetLastError(); + } + +done: + CloseHandle(jobObject); + + return dwError; +} + +DWORD ChownImpl( + __in_opt LPCWSTR userName, + __in_opt LPCWSTR groupName, + __in LPCWSTR pathName) { + + DWORD dwError; + + PSID pNewOwnerSid = NULL; + PSID pNewGroupSid = NULL; + + if (userName != NULL) + { + dwError = GetSidFromAcctNameW(userName, &pNewOwnerSid); + if (dwError != ERROR_SUCCESS) + { + ReportErrorCode(L"GetSidFromAcctName", dwError); + fwprintf(stderr, L"Invalid user name: %s\n", userName); + goto done; + } + } + + if (groupName != NULL) + { + dwError = GetSidFromAcctNameW(groupName, &pNewGroupSid); + if (dwError != ERROR_SUCCESS) + { + ReportErrorCode(L"GetSidFromAcctName", dwError); + fwprintf(stderr, L"Invalid group name: %s\n", groupName); + goto done; + } + } + + if (wcslen(pathName) == 0 || wcsspn(pathName, L"/?|><:*\"") != 0) + { + fwprintf(stderr, L"Incorrect file name format: %s\n", pathName); + goto done; + } + + dwError = ChangeFileOwnerBySid(pathName, pNewOwnerSid, pNewGroupSid); + if (dwError != ERROR_SUCCESS) + { + ReportErrorCode(L"ChangeFileOwnerBySid", dwError); + goto done; + } +done: + LocalFree(pNewOwnerSid); + LocalFree(pNewGroupSid); + + return dwError; +} + + + +LPCWSTR GetSystemTimeString() { + __declspec(thread) static WCHAR buffer[1024]; + DWORD dwError; + FILETIME ftime; + SYSTEMTIME systime; + LARGE_INTEGER counter, frequency; + int subSec; + double qpc; + HRESULT hr; + buffer[0] = L'\0'; + + // GetSystemTimePreciseAsFileTime is only available in Win8+ and our libs do not link against it + + GetSystemTimeAsFileTime(&ftime); + + if (!FileTimeToSystemTime(&ftime, &systime)) { + dwError = GetLastError(); + LogDebugMessage(L"FileTimeToSystemTime error:%d\n", dwError); + goto done; + } + + // Get the ms from QPC. GetSystemTimeAdjustment is ignored... + + QueryPerformanceCounter(&counter); + QueryPerformanceFrequency(&frequency); + + qpc = (double) counter.QuadPart / (double) frequency.QuadPart; + subSec = ((qpc - (long)qpc) * 1000000); + + hr = StringCbPrintf(buffer, sizeof(buffer), L"%02d:%02d:%02d.%06d", + (int)systime.wHour, (int)systime.wMinute, (int)systime.wSecond, (int)subSec); + + if (FAILED(hr)) { + LogDebugMessage(L"StringCbPrintf error:%d\n", hr); + } +done: + return buffer; +} + + +//---------------------------------------------------------------------------- +// Function: LogDebugMessage +// +// Description: +// Sends a message to the debugger console, if one is attached +// +// Notes: +// Native debugger: windbg, ntsd, cdb, visual studio +// +VOID LogDebugMessage(LPCWSTR format, ...) { + LPWSTR buffer[8192]; + va_list args; + HRESULT hr; + + if (!IsDebuggerPresent()) return; + + va_start(args, format); + hr = StringCbVPrintf(buffer, sizeof(buffer), format, args); + if (SUCCEEDED(hr)) { + OutputDebugString(buffer); + } + va_end(args); +} + +//---------------------------------------------------------------------------- +// Function: SplitStringIgnoreSpaceW +// +// Description: +// splits a null-terminated string based on a delimiter +// +// Returns: +// ERROR_SUCCESS: on success +// error code: otherwise +// +// Notes: +// The tokes are also null-terminated +// Caller should use LocalFree to clear outTokens +// +DWORD SplitStringIgnoreSpaceW( + __in size_t len, + __in_ecount(len) LPCWSTR source, + __in WCHAR deli, + __out size_t* count, + __out_ecount(count) WCHAR*** outTokens) { + + size_t tokenCount = 0; + size_t crtSource; + size_t crtToken = 0; + WCHAR* lpwszTokenStart = NULL; + WCHAR* lpwszTokenEnd = NULL; + WCHAR* lpwszBuffer = NULL; + size_t tokenLength = 0; + size_t cchBufferLength = 0; + WCHAR crt; + WCHAR** tokens = NULL; + enum {BLANK, TOKEN, DELIMITER} State = BLANK; + + for(crtSource = 0; crtSource < len; ++crtSource) { + crt = source[crtSource]; + switch(State) { + case BLANK: // intentional fallthrough + case DELIMITER: + if (crt == deli) { + State = DELIMITER; + } + else if (!iswspace(crt)) { + ++tokenCount; + lpwszTokenEnd = lpwszTokenStart = source + crtSource; + State = TOKEN; + } + else { + State = BLANK; + } + break; + case TOKEN: + if (crt == deli) { + State = DELIMITER; + cchBufferLength += lpwszTokenEnd - lpwszTokenStart + 2; + } + else if (!iswspace(crt)) { + lpwszTokenEnd = source + crtSource; + } + break; + } + } + + if (State == TOKEN) { + cchBufferLength += lpwszTokenEnd - lpwszTokenStart + 2; + } + + LogDebugMessage(L"counted %d [buffer:%d] tokens in %s\n", tokenCount, cchBufferLength, source); + + #define COPY_CURRENT_TOKEN \ + tokenLength = lpwszTokenEnd - lpwszTokenStart + 1; \ + tokens[crtToken] = lpwszBuffer; \ + memcpy(tokens[crtToken], lpwszTokenStart, tokenLength*sizeof(WCHAR)); \ + tokens[crtToken][tokenLength] = L'\0'; \ + lpwszBuffer += (tokenLength+1); \ + ++crtToken; + + if (tokenCount) { + + // We use one contigous memory for both the pointer arrays and the data copy buffers + // We cannot use in-place references (zero-copy) because the function users + // need null-terminated strings for the tokens + + tokens = (WCHAR**) LocalAlloc(LPTR, + sizeof(WCHAR*) * tokenCount + // for the pointers + sizeof(WCHAR) * cchBufferLength); // for the data + + // Data will be copied after the array + lpwszBuffer = (WCHAR*)(((BYTE*)tokens) + (sizeof(WCHAR*) * tokenCount)); + + State = BLANK; + + for(crtSource = 0; crtSource < len; ++crtSource) { + crt = source[crtSource]; + switch(State) { + case DELIMITER: // intentional fallthrough + case BLANK: + if (crt == deli) { + State = DELIMITER; + } + else if (!iswspace(crt)) { + lpwszTokenEnd = lpwszTokenStart = source + crtSource; + State = TOKEN; + } + else { + State = BLANK; + } + break; + case TOKEN: + if (crt == deli) { + COPY_CURRENT_TOKEN; + State = DELIMITER; + } + else if (!iswspace(crt)) { + lpwszTokenEnd = source + crtSource; + } + break; + } + } + + // Copy out last token, if any + if (TOKEN == State) { + COPY_CURRENT_TOKEN; + } + } + + *count = tokenCount; + *outTokens = tokens; + + return ERROR_SUCCESS; +} + +//---------------------------------------------------------------------------- +// Function: BuildServiceSecurityDescriptor +// +// Description: +// Builds a security descriptor for an arbitrary object +// +// Returns: +// ERROR_SUCCESS: on success +// error code: otherwise +// +// Notes: +// The SD is a of the self-contained flavor (offsets, not pointers) +// Caller should use LocalFree to clear allocated pSD +// +DWORD BuildServiceSecurityDescriptor( + __in ACCESS_MASK accessMask, + __in size_t grantSidCount, + __in_ecount(grantSidCount) PSID* pGrantSids, + __in size_t denySidCount, + __in_ecount(denySidCount) PSID* pDenySids, + __in_opt PSID pOwner, + __out PSECURITY_DESCRIPTOR* pSD) { + + DWORD dwError = ERROR_SUCCESS; + int crt = 0; + int len = 0; + EXPLICIT_ACCESS* eas = NULL; + LPWSTR lpszSD = NULL; + ULONG cchSD = 0; + HANDLE hToken = INVALID_HANDLE_VALUE; + DWORD dwBufferSize = 0; + PTOKEN_USER pTokenUser = NULL; + PTOKEN_PRIMARY_GROUP pTokenGroup = NULL; + PSECURITY_DESCRIPTOR pTempSD = NULL; + ULONG cbSD = 0; + TRUSTEE owner, group; + + ZeroMemory(&owner, sizeof(owner)); + + // We'll need our own SID to add as SD owner + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { + dwError = GetLastError(); + LogDebugMessage(L"OpenProcessToken: %d\n", dwError); + goto done; + } + + if (NULL == pOwner) { + if (!GetTokenInformation(hToken, TokenUser, NULL, 0, &dwBufferSize)) { + dwError = GetLastError(); + if (ERROR_INSUFFICIENT_BUFFER != dwError) { + LogDebugMessage(L"GetTokenInformation: %d\n", dwError); + goto done; + } + } + + pTokenUser = (PTOKEN_USER) LocalAlloc(LPTR, dwBufferSize); + if (NULL == pTokenUser) { + dwError = GetLastError(); + LogDebugMessage(L"LocalAlloc:pTokenUser: %d\n", dwError); + goto done; + } + + if (!GetTokenInformation(hToken, TokenUser, pTokenUser, dwBufferSize, &dwBufferSize)) { + dwError = GetLastError(); + LogDebugMessage(L"GetTokenInformation: %d\n", dwError); + goto done; + } + + if (!IsValidSid(pTokenUser->User.Sid)) { + dwError = ERROR_INVALID_PARAMETER; + LogDebugMessage(L"IsValidSid: %d\n", dwError); + goto done; + } + pOwner = pTokenUser->User.Sid; + } + + dwBufferSize = 0; + if (!GetTokenInformation(hToken, TokenPrimaryGroup, NULL, 0, &dwBufferSize)) { + dwError = GetLastError(); + if (ERROR_INSUFFICIENT_BUFFER != dwError) { + LogDebugMessage(L"GetTokenInformation: %d\n", dwError); + goto done; + } + } + + pTokenGroup = (PTOKEN_USER) LocalAlloc(LPTR, dwBufferSize); + if (NULL == pTokenGroup) { + dwError = GetLastError(); + LogDebugMessage(L"LocalAlloc:pTokenGroup: %d\n", dwError); + goto done; + } + + if (!GetTokenInformation(hToken, TokenPrimaryGroup, pTokenGroup, dwBufferSize, &dwBufferSize)) { + dwError = GetLastError(); + LogDebugMessage(L"GetTokenInformation: %d\n", dwError); + goto done; + } + + if (!IsValidSid(pTokenGroup->PrimaryGroup)) { + dwError = ERROR_INVALID_PARAMETER; + LogDebugMessage(L"IsValidSid: %d\n", dwError); + goto done; + } + + owner.TrusteeForm = TRUSTEE_IS_SID; + owner.TrusteeType = TRUSTEE_IS_UNKNOWN; + owner.ptstrName = (LPCWSTR) pOwner; + + group.TrusteeForm = TRUSTEE_IS_SID; + group.TrusteeType = TRUSTEE_IS_UNKNOWN; + group.ptstrName = (LPCWSTR) pTokenGroup->PrimaryGroup; + + eas = (EXPLICIT_ACCESS*) LocalAlloc(LPTR, sizeof(EXPLICIT_ACCESS) * (grantSidCount + denySidCount)); + if (NULL == eas) { + dwError = ERROR_OUTOFMEMORY; + LogDebugMessage(L"LocalAlloc: %d\n", dwError); + goto done; + } + + // Build the granted list + for (crt = 0; crt < grantSidCount; ++crt) { + eas[crt].grfAccessPermissions = accessMask; + eas[crt].grfAccessMode = GRANT_ACCESS; + eas[crt].grfInheritance = NO_INHERITANCE; + eas[crt].Trustee.TrusteeForm = TRUSTEE_IS_SID; + eas[crt].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN; + eas[crt].Trustee.ptstrName = (LPCWSTR) pGrantSids[crt]; + eas[crt].Trustee.pMultipleTrustee = NULL; + eas[crt].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + } + + // Build the deny list + for (; crt < grantSidCount + denySidCount; ++crt) { + eas[crt].grfAccessPermissions = accessMask; + eas[crt].grfAccessMode = DENY_ACCESS; + eas[crt].grfInheritance = NO_INHERITANCE; + eas[crt].Trustee.TrusteeForm = TRUSTEE_IS_SID; + eas[crt].Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN; + eas[crt].Trustee.ptstrName = (LPCWSTR) pDenySids[crt - grantSidCount]; + eas[crt].Trustee.pMultipleTrustee = NULL; + eas[crt].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + } + + dwError = BuildSecurityDescriptor( + &owner, + &group, + crt, + eas, + 0, // cCountOfAuditEntries + NULL, // pListOfAuditEntries + NULL, // pOldSD + &cbSD, + &pTempSD); + if (ERROR_SUCCESS != dwError) { + LogDebugMessage(L"BuildSecurityDescriptor: %d\n", dwError); + goto done; + } + + *pSD = pTempSD; + pTempSD = NULL; + + if (IsDebuggerPresent()) { + ConvertSecurityDescriptorToStringSecurityDescriptor(*pSD, + SDDL_REVISION_1, + DACL_SECURITY_INFORMATION, + &lpszSD, + &cchSD); + LogDebugMessage(L"pSD: %.*s\n", cchSD, lpszSD); + } + +done: + if (eas) LocalFree(eas); + if (pTokenUser) LocalFree(pTokenUser); + if (INVALID_HANDLE_VALUE != hToken) CloseHandle(hToken); + if (lpszSD) LocalFree(lpszSD); + if (pTempSD) LocalFree(pTempSD); + return dwError; +} + +//---------------------------------------------------------------------------- +// Function: MIDL_user_allocate +// +// Description: +// Hard-coded function name used by RPC midl code for allocations +// +// Notes: +// Must match the de-allocation mechanism used in MIDL_user_free +// +void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t len) +{ + return LocalAlloc(LPTR, len); +} + + //---------------------------------------------------------------------------- + // Function: MIDL_user_free + // + // Description: + // Hard-coded function name used by RPC midl code for deallocations + // + // NoteS: + // Must match the allocation mechanism used in MIDL_user_allocate + // +void __RPC_USER MIDL_user_free(void __RPC_FAR * ptr) +{ + LocalFree(ptr); +} + diff --git a/hadoop-common-project/hadoop-common/src/main/winutils/libwinutils.vcxproj b/hadoop-common-project/hadoop-common/src/main/winutils/libwinutils.vcxproj index fc0519dff7f..37b7f312005 100644 --- a/hadoop-common-project/hadoop-common/src/main/winutils/libwinutils.vcxproj +++ b/hadoop-common-project/hadoop-common/src/main/winutils/libwinutils.vcxproj @@ -19,18 +19,10 @@ - - Debug - Win32 - Debug x64 - - Release - Win32 - Release x64 @@ -42,22 +34,11 @@ winutils - - StaticLibrary - true - Unicode - StaticLibrary true Unicode - - StaticLibrary - false - true - Unicode - StaticLibrary false @@ -67,15 +48,9 @@ - - - - - - @@ -83,74 +58,35 @@ include;$(IncludePath) - - true - true - - ..\..\..\target\winutils\$(Configuration)\ - - - false false - ..\..\..\target\bin\ - ..\..\..\target\winutils\$(Platform)\$(Configuration)\ - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - - Level4 Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + WIN32;_DEBUG;_UNICODE;UNICODE;WSCE_CONFIG_DIR=$(WsceConfigDir);WSCE_CONFIG_FILE=$(WsceConfigFile);%(PreprocessorDefinitions) Console true - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - true - true - - Level3 - MaxSpeed + + Disabled true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + WIN32;NDEBUG;_UNICODE;UNICODE;WSCE_CONFIG_DIR=$(WsceConfigDir);WSCE_CONFIG_FILE=$(WsceConfigFile);%(PreprocessorDefinitions) Console @@ -159,12 +95,34 @@ true + + + $(IntermediateOutputPath) + + + true + X64 + $(IntermediateOutputPath) + true + true + true + 2 + + + + + + + + + + diff --git a/hadoop-common-project/hadoop-common/src/main/winutils/main.c b/hadoop-common-project/hadoop-common/src/main/winutils/main.c index 0f407743ff8..ac73aeccebc 100644 --- a/hadoop-common-project/hadoop-common/src/main/winutils/main.c +++ b/hadoop-common-project/hadoop-common/src/main/winutils/main.c @@ -17,12 +17,27 @@ #include "winutils.h" +#include + static void Usage(LPCWSTR program); +LONG WINAPI WinutilsSehUnhandled(_In_ struct _EXCEPTION_POINTERS *ecxr) { + LogDebugMessage(L"unhandled SEH: code:%x flags:%d\n", + ecxr->ExceptionRecord->ExceptionCode, + ecxr->ExceptionRecord->ExceptionFlags); + fwprintf(stderr, L"Unhandled exception code:%x at address:%p", + ecxr->ExceptionRecord->ExceptionCode, + ecxr->ExceptionRecord->ExceptionAddress); + ExitProcess(ERROR_UNHANDLED_EXCEPTION); + return EXCEPTION_EXECUTE_HANDLER; // not that it matters... +} + int wmain(__in int argc, __in_ecount(argc) wchar_t* argv[]) { LPCWSTR cmd = NULL; + SetUnhandledExceptionFilter(WinutilsSehUnhandled); + if (argc < 2) { Usage(argv[0]); @@ -67,6 +82,10 @@ int wmain(__in int argc, __in_ecount(argc) wchar_t* argv[]) { return SystemInfo(); } + else if (wcscmp(L"service", cmd) == 0) + { + return RunService(argc - 1, argv + 1); + } else if (wcscmp(L"help", cmd) == 0) { Usage(argv[0]); @@ -119,5 +138,9 @@ The available commands and their usages are:\n\n", program); fwprintf(stdout, L"%-15s%s\n\n", L"task", L"Task operations."); TaskUsage(); + + fwprintf(stdout, L"%-15s%s\n\n", L"service", L"Service operations."); + ServiceUsage(); + fwprintf(stdout, L"\n\n"); } diff --git a/hadoop-common-project/hadoop-common/src/main/winutils/service.c b/hadoop-common-project/hadoop-common/src/main/winutils/service.c new file mode 100644 index 00000000000..a0f8a66c6a2 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/winutils/service.c @@ -0,0 +1,1485 @@ +/** +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with this +* work for additional information regarding copyright ownership. The ASF +* licenses this file to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +* License for the specific language governing permissions and limitations under +* the License. +*/ + +#include "winutils.h" +#include "winutils_msg.h" +#include +#include +#include +#include +#include +#include +#include "hadoopwinutilsvc_h.h" + +#pragma comment(lib, "Rpcrt4.lib") +#pragma comment(lib, "advapi32.lib") +#pragma comment(lib, "authz.lib") + +LPCWSTR NM_WSCE_ALLOWED = L"yarn.nodemanager.windows-secure-container-executor.allowed"; +LPCWSTR NM_WSCE_JOB_NAME = L"yarn.nodemanager.windows-secure-container-executor.job-name"; +LPCWSTR NM_WSCE_LOCAL_DIRS = L"yarn.nodemanager.windows-secure-container-executor.local-dirs"; + +#define SERVICE_ACCESS_MASK 0x00000001 + +SERVICE_STATUS gSvcStatus; +SERVICE_STATUS_HANDLE gSvcStatusHandle; +HANDLE ghSvcStopEvent = INVALID_HANDLE_VALUE; +HANDLE ghWaitObject = INVALID_HANDLE_VALUE; +HANDLE ghEventLog = INVALID_HANDLE_VALUE; +BOOL isListenning = FALSE; +PSECURITY_DESCRIPTOR pAllowedSD = NULL; +LPWSTR* gLocalDirs = NULL; +size_t gLocalDirsCount = 0; +int* gCchLocalDir = NULL; +LPCWSTR gJobName = NULL; + +VOID SvcError(DWORD dwError); +VOID WINAPI SvcMain(DWORD dwArg, LPTSTR* lpszArgv); +DWORD SvcInit(); +DWORD RpcInit(); +DWORD AuthInit(); +VOID ReportSvcStatus( DWORD dwCurrentState, + DWORD dwWin32ExitCode, + DWORD dwWaitHint); +VOID WINAPI SvcCtrlHandler( DWORD dwCtrl ); +VOID CALLBACK SvcShutdown( + _In_ PVOID lpParameter, + _In_ BOOLEAN TimerOrWaitFired); + +#define CHECK_ERROR_DONE(status, expected, category, message) \ + if (status != expected) { \ + ReportSvcCheckError( \ + EVENTLOG_ERROR_TYPE, \ + category, \ + status, \ + message); \ + goto done; \ + } else { \ + LogDebugMessage(L"%s: OK\n", message); \ + } + + +#define CHECK_RPC_STATUS_DONE(status, message) \ + CHECK_ERROR_DONE(status, RPC_S_OK, SERVICE_CATEGORY, message) + +#define CHECK_SVC_STATUS_DONE(status, message) \ + CHECK_ERROR_DONE(status, ERROR_SUCCESS, SERVICE_CATEGORY, message) + +#define CHECK_UNWIND_RPC(rpcCall) { \ + unwindStatus = rpcCall; \ + if (RPC_S_OK != unwindStatus) { \ + ReportSvcCheckError( \ + EVENTLOG_WARNING_TYPE, \ + SERVICE_CATEGORY, \ + unwindStatus, \ + L#rpcCall); \ + } \ + } + +//---------------------------------------------------------------------------- +// Function: ReportSvcCheckError +// +// Description: +// Reports an error with the system event log and to debugger console (if present) +// +void ReportSvcCheckError(WORD type, WORD category, DWORD dwError, LPCWSTR message) { + int len; + LPWSTR systemMsg = NULL; + LPWSTR appMsg = NULL; + DWORD dwReportError; + LPWSTR reportMsg = NULL; + WCHAR hexError[32]; + LPCWSTR inserts[] = {message, NULL, NULL, NULL}; + HRESULT hr; + + hr = StringCbPrintf(hexError, sizeof(hexError), TEXT("%x"), dwError); + if (SUCCEEDED(hr)) { + inserts[1] = hexError; + } + else { + inserts[1] = L"(Failed to format dwError as string)"; + } + + len = FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, dwError, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&systemMsg, 0, NULL); + + if (len) { + inserts[2] = systemMsg; + } + else { + inserts[2] = L"(Failed to get the system error message)"; + } + + LogDebugMessage(L"%s:%d %.*s\n", message, dwError, len, systemMsg); + + if (INVALID_HANDLE_VALUE != ghEventLog) { + if (!ReportEvent(ghEventLog, type, category, MSG_CHECK_ERROR, + NULL, // lpUserSid + (WORD) 3, // wNumStrings + (DWORD) 0, // dwDataSize + inserts, // *lpStrings + NULL // lpRawData + )) { + // We tried to report and failed. Send to dbg. + dwReportError = GetLastError(); + len = FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, dwReportError, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&reportMsg, 0, NULL); + LogDebugMessage(L"ReportEvent: Error:%d %.*s\n", dwReportError, reportMsg); + } + }; + + if (NULL != systemMsg) LocalFree(systemMsg); + if (NULL != reportMsg) LocalFree(reportMsg); +} + + +VOID ReportSvcMessage(WORD type, WORD category, DWORD msgId) { + DWORD dwError; + + if (INVALID_HANDLE_VALUE != ghEventLog) { + if (!ReportEvent(ghEventLog, type, category, msgId, + NULL, // lpUserSid + (WORD) 0, // wNumStrings + (DWORD) 0, // dwDataSize + NULL, // *lpStrings + NULL // lpRawData + )) { + // We tried to report and failed but debugger is attached. Send to dbg. + dwError = GetLastError(); + LogDebugMessage(L"ReportEvent: error %d\n", dwError); + } + } +} + +//---------------------------------------------------------------------------- +// Function: IsSidInList +// +// Description: +// Finds a SID in an array of SID* +// +BOOL IsSidInList( + __in PSID trustee, + __in size_t cAllowedSids, + __in_ecount(cAllowedSids) PSID* allowedSids) { + + int crtSid = 0; + + for (crtSid = 0; crtSid < cAllowedSids; ++crtSid) { + if (EqualSid(trustee, allowedSids[crtSid])) { + return TRUE; + } + } + return FALSE; +} + + +//---------------------------------------------------------------------------- +// Function: InitLocalDirs +// +// Description: +// Validates that the wsceConfigRelativePath file is only writable by Administrators +// +DWORD ValidateConfigurationFile() { + DWORD dwError = ERROR_SUCCESS; + WCHAR xmlPath[MAX_PATH]; + PSECURITY_DESCRIPTOR pSd = NULL; + BOOL daclPresent = FALSE; + BOOL daclDefaulted = FALSE; + PACL pDacl = NULL; + int crt = 0, crtSid = 0; + WELL_KNOWN_SID_TYPE allowedSidTypes[] = { + WinLocalSystemSid, + WinBuiltinAdministratorsSid}; + ACL_SIZE_INFORMATION aclInfo; + DWORD cbSid = SECURITY_MAX_SID_SIZE; + PSID* allowedSids = NULL; + int cAllowedSids = 0; + BOOL isSidDefaulted; + PSID sidOwner = NULL; + PSID sidGroup = NULL; + + allowedSids = (PSID*) LocalAlloc( + LPTR, + sizeof(PSID) * sizeof(allowedSidTypes) / sizeof(WELL_KNOWN_SID_TYPE)); + if (NULL == allowedSids) { + dwError = ERROR_OUTOFMEMORY; + CHECK_SVC_STATUS_DONE(dwError, L"LocalAlloc"); + } + + for(crt = 0; crt < sizeof(allowedSidTypes) / sizeof(WELL_KNOWN_SID_TYPE); ++crt) { + allowedSids[crt] = LocalAlloc(LPTR, SECURITY_MAX_SID_SIZE); + if (NULL == allowedSids[crt]) { + dwError = ERROR_OUTOFMEMORY; + CHECK_SVC_STATUS_DONE(dwError, L"LocalAlloc"); + } + + cbSid = SECURITY_MAX_SID_SIZE; + + if (!CreateWellKnownSid( + allowedSidTypes[crt], NULL, allowedSids[crt], &cbSid)) { + dwError = GetLastError(); + CHECK_SVC_STATUS_DONE(dwError, L"CreateWellKnownSid"); + } + ++cAllowedSids; + } + + dwError = BuildPathRelativeToModule( + wsceConfigRelativePath, + sizeof(xmlPath)/sizeof(WCHAR), + xmlPath); + CHECK_SVC_STATUS_DONE(dwError, L"BuildPathRelativeToModule"); + + dwError = GetNamedSecurityInfo( + xmlPath, + SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, NULL, NULL, NULL, &pSd); + CHECK_SVC_STATUS_DONE(dwError, L"GetNamedSecurityInfo"); + + if (!GetSecurityDescriptorDacl( + pSd, + &daclPresent, + &pDacl, + &daclDefaulted)) { + dwError = GetLastError(); + CHECK_SVC_STATUS_DONE(dwError, L"GetSecurityDescriptorDacl"); + } + + if (!pDacl) { + dwError = ERROR_BAD_CONFIGURATION; + CHECK_SVC_STATUS_DONE(dwError, L"pDacl"); + } + + ZeroMemory(&aclInfo, sizeof(aclInfo)); + if (!GetAclInformation(pDacl, &aclInfo, sizeof(aclInfo), AclSizeInformation)) { + dwError = GetLastError(); + CHECK_SVC_STATUS_DONE(dwError, L"GetAclInformation"); + } + + // Inspect all ACEs in the file DACL. + // Look at all WRITE GRANTs. Make sure the trustee Sid is one of the approved Sid + // + for(crt = 0; crt < aclInfo.AceCount; ++crt) { + + ACE_HEADER* aceHdr = NULL; + if (!GetAce(pDacl, crt, &aceHdr)) { + dwError = GetLastError(); + CHECK_SVC_STATUS_DONE(dwError, L"GetAce"); + } + + if (ACCESS_ALLOWED_ACE_TYPE == aceHdr->AceType) { + ACCESS_ALLOWED_ACE* pAce = (ACCESS_ALLOWED_ACE*) aceHdr; + if (WinMasks[WIN_WRITE] & pAce->Mask) { + if (!IsSidInList((PSID) &pAce->SidStart, cAllowedSids, allowedSids)) { + dwError = ERROR_BAD_CONFIGURATION; + CHECK_SVC_STATUS_DONE(dwError, L"!validSidFound"); + } + } + } + } + +done: + if (pSd) LocalFree(pSd); + + if (allowedSids) { + while (cAllowedSids) { + LocalFree(allowedSids[cAllowedSids--]); + } + LocalFree(allowedSids); + } + + return dwError; +} + +//---------------------------------------------------------------------------- +// Function: InitJobName +// +// Description: +// Loads the job name to be used for created processes +// +DWORD InitJobName() { + DWORD dwError = ERROR_SUCCESS; + size_t len = 0; + LPCWSTR value = NULL; + int crt = 0; + + // Services can be restarted + if (gJobName) LocalFree(gJobName); + gJobName = NULL; + + dwError = GetConfigValue( + wsceConfigRelativePath, + NM_WSCE_JOB_NAME, &len, &value); + CHECK_SVC_STATUS_DONE(dwError, L"GetConfigValue"); + + if (len) { + gJobName = value; + } +done: + return dwError; +} + + +//---------------------------------------------------------------------------- +// Function: InitLocalDirs +// +// Description: +// Loads the configured local dirs +// +DWORD InitLocalDirs() { + DWORD dwError = ERROR_SUCCESS; + size_t len = 0; + LPCWSTR value = NULL; + int crt = 0; + + + dwError = GetConfigValue( + wsceConfigRelativePath, + NM_WSCE_LOCAL_DIRS, &len, &value); + CHECK_SVC_STATUS_DONE(dwError, L"GetConfigValue"); + + if (0 == len) { + dwError = ERROR_BAD_CONFIGURATION; + CHECK_SVC_STATUS_DONE(dwError, NM_WSCE_LOCAL_DIRS); + } + + dwError = SplitStringIgnoreSpaceW(len, value, L',', &gLocalDirsCount, &gLocalDirs); + CHECK_SVC_STATUS_DONE(dwError, L"SplitStringIgnoreSpaceW"); + + if (0 == gLocalDirsCount) { + dwError = ERROR_BAD_CONFIGURATION; + CHECK_SVC_STATUS_DONE(dwError, NM_WSCE_LOCAL_DIRS); + } + + gCchLocalDir = (int*) LocalAlloc(LPTR, sizeof(int) * gLocalDirsCount); + if (NULL == gCchLocalDir) { + dwError = ERROR_OUTOFMEMORY; + CHECK_SVC_STATUS_DONE(dwError, L"LocalAlloc"); + } + + for (crt = 0; crt < gLocalDirsCount; ++crt) { + gCchLocalDir[crt] = (int) wcsnlen(gLocalDirs[crt], MAX_PATH); + } + +done: + if (value) LocalFree(value); + + return dwError; +} + +//---------------------------------------------------------------------------- +// Function: ValidateLocalPath +// +// Description: +// Validates that a path is within the contained local dirs +// +DWORD ValidateLocalPath(LPCWSTR lpszPath) { + DWORD dwError = ERROR_SUCCESS; + int compareResult = 0; + int crt = 0; + int cchLocalBuffer = 0; + WCHAR localBuffer[MAX_PATH+1]; + BOOLEAN nullFound = FALSE; + + // Make a copy of the path and replace / with \ in the process + while(crt < MAX_PATH && !nullFound) { + switch(lpszPath[crt]) { + case L'/': + localBuffer[crt] = L'\\'; + ++crt; + break; + case L'\0': + // NULL terminator + nullFound = TRUE; + break; + default: + localBuffer[crt] = lpszPath[crt]; + ++crt; + break; + } + } + + if (FALSE == nullFound) { + dwError = ERROR_BUFFER_OVERFLOW; + CHECK_SVC_STATUS_DONE(dwError, L"localBuffer"); + } + + localBuffer[crt] = 0; + cchLocalBuffer = crt; + + for(crt = 0; crt < gLocalDirsCount; ++crt) { + + // use max len gCchLocalDir[crt] to see if it starts with this local dir + compareResult = CompareStringEx( + LOCALE_NAME_INVARIANT, + NORM_IGNORECASE, + localBuffer, gCchLocalDir[crt] <= cchLocalBuffer ? gCchLocalDir[crt] : cchLocalBuffer, + gLocalDirs[crt], gCchLocalDir[crt], + NULL, // lpVersionInformation + NULL, // lpReserved + NULL); // lParam + + if (0 == compareResult) { + dwError = GetLastError(); + CHECK_SVC_STATUS_DONE(dwError, L"CompareStringEx"); + } + + if (CSTR_EQUAL == compareResult) { + break; + } + } + + if (CSTR_EQUAL != compareResult) { + LogDebugMessage(L"ValidateLocalPath bad path: %s\n", lpszPath); + dwError = ERROR_BAD_PATHNAME; + } + +done: + return dwError; +} + + + +//---------------------------------------------------------------------------- +// Function: RunService +// +// Description: +// Registers with NT SCM and starts the service +// +// Returns: +// ERROR_SUCCESS: On success +// Error code otherwise: otherwise +DWORD RunService(__in int argc, __in_ecount(argc) wchar_t *argv[]) +{ + DWORD dwError= ERROR_SUCCESS; + int argStart = 1; + + static const SERVICE_TABLE_ENTRY serviceTable[] = { + { SVCNAME, (LPSERVICE_MAIN_FUNCTION) SvcMain }, + { NULL, NULL } + }; + + ghEventLog = RegisterEventSource(NULL, SVCNAME); + if (NULL == ghEventLog) { + dwError = GetLastError(); + CHECK_SVC_STATUS_DONE(dwError, L"RegisterEventSource") + } + + if (!StartServiceCtrlDispatcher(serviceTable)) { + dwError = GetLastError(); + CHECK_SVC_STATUS_DONE(dwError, L"StartServiceCtrlDispatcher") + } + +done: + return dwError; +} + +//---------------------------------------------------------------------------- +// Function: SvcMain +// +// Description: +// Service main entry point. +// +VOID WINAPI SvcMain() { + DWORD dwError = ERROR_SUCCESS; + + gSvcStatusHandle = RegisterServiceCtrlHandler( + SVCNAME, + SvcCtrlHandler); + if( !gSvcStatusHandle ) { + dwError = GetLastError(); + CHECK_SVC_STATUS_DONE(dwError, L"RegisterServiceCtrlHandler") + } + + // These SERVICE_STATUS members remain as set here + gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + gSvcStatus.dwServiceSpecificExitCode = 0; + + // Report initial status to the SCM + ReportSvcStatus( SERVICE_START_PENDING, NO_ERROR, 3000 ); + + // Perform service-specific initialization and work. + dwError = SvcInit(); + +done: + return; +} + +//---------------------------------------------------------------------------- +// Function: SvcInit +// +// Description: +// Initializes the service. +// +DWORD SvcInit() { + DWORD dwError = ERROR_SUCCESS; + + dwError = EnableImpersonatePrivileges(); + if( dwError != ERROR_SUCCESS ) { + ReportErrorCode(L"EnableImpersonatePrivileges", dwError); + goto done; + } + + // The recommended way to shutdown the service is to use an event + // and attach a callback with RegisterWaitForSingleObject + // + ghSvcStopEvent = CreateEvent( + NULL, // default security attributes + TRUE, // manual reset event + FALSE, // not signaled + NULL); // no name + + if ( ghSvcStopEvent == NULL) + { + dwError = GetLastError(); + ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, + dwError, L"CreateEvent"); + ReportSvcStatus( SERVICE_STOPPED, dwError, 0 ); + goto done; + } + + if (!RegisterWaitForSingleObject (&ghWaitObject, + ghSvcStopEvent, + SvcShutdown, + NULL, + INFINITE, + WT_EXECUTEONLYONCE)) { + dwError = GetLastError(); + ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, + dwError, L"RegisterWaitForSingleObject"); + CloseHandle(ghSvcStopEvent); + ReportSvcStatus( SERVICE_STOPPED, dwError, 0 ); + goto done; + } + + dwError = ValidateConfigurationFile(); + if (ERROR_SUCCESS != dwError) { + LogDebugMessage(L"ValidateConfigurationFile failed: %d", dwError); + SvcError(dwError); + goto done; + } + + dwError = AuthInit(); + if (ERROR_SUCCESS != dwError) { + LogDebugMessage(L"AuthInit failed: %d", dwError); + SvcError(dwError); + goto done; + } + + dwError = InitLocalDirs(); + if (ERROR_SUCCESS != dwError) { + LogDebugMessage(L"InitLocalDirs failed: %d", dwError); + SvcError(dwError); + goto done; + } + + dwError = InitJobName(); + if (ERROR_SUCCESS != dwError) { + LogDebugMessage(L"InitJobName failed: %d", dwError); + SvcError(dwError); + goto done; + } + + // Report running status when initialization is complete. + ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 ); + + dwError = RpcInit(); + +done: + return dwError; +} + +//---------------------------------------------------------------------------- +// Function: RpcAuthorizeCallback +// +// Description: +// RPC Authorization callback. +// +// Returns: +// RPC_S_OK for access authorized +// RPC_S_ACCESS_DENIED for access denied +// +RPC_STATUS CALLBACK RpcAuthorizeCallback ( + RPC_IF_HANDLE hInterface, + void* pContext) +{ + RPC_STATUS status, + unwindStatus, + authStatus = RPC_S_ACCESS_DENIED; + DWORD dwError; + LUID luidReserved2; + AUTHZ_ACCESS_REQUEST request; + AUTHZ_ACCESS_REPLY reply; + AUTHZ_CLIENT_CONTEXT_HANDLE hClientContext = NULL; + DWORD authError = ERROR_SUCCESS; + DWORD saclResult = 0; + ACCESS_MASK grantedMask = 0; + + ZeroMemory(&luidReserved2, sizeof(luidReserved2)); + ZeroMemory(&request, sizeof(request)); + ZeroMemory(&reply, sizeof(reply)); + + status = RpcGetAuthorizationContextForClient(NULL, + FALSE, // ImpersonateOnReturn + NULL, // Reserved1 + NULL, // pExpirationTime + luidReserved2, // Reserved2 + 0, // Reserved3 + NULL, // Reserved4 + &hClientContext); + CHECK_RPC_STATUS_DONE(status, L"RpcGetAuthorizationContextForClient"); + + request.DesiredAccess = MAXIMUM_ALLOWED; + reply.Error = &authError; + reply.SaclEvaluationResults = &saclResult; + reply.ResultListLength = 1; + reply.GrantedAccessMask = &grantedMask; + + if (!AuthzAccessCheck( + 0, + hClientContext, + &request, + NULL, // AuditEvent + pAllowedSD, + NULL, // OptionalSecurityDescriptorArray + 0, // OptionalSecurityDescriptorCount + &reply, + NULL // phAccessCheckResults + )) { + dwError = GetLastError(); + CHECK_SVC_STATUS_DONE(dwError, L"AuthzAccessCheck"); + } + + LogDebugMessage(L"AutzAccessCheck: Error:%d sacl:%d access:%d\n", + authError, saclResult, grantedMask); + if (authError == ERROR_SUCCESS && (grantedMask & SERVICE_ACCESS_MASK)) { + authStatus = RPC_S_OK; + } + +done: + if (NULL != hClientContext) CHECK_UNWIND_RPC(RpcFreeAuthorizationContext(&hClientContext)); + return authStatus; +} + +//---------------------------------------------------------------------------- +// Function: AuthInit +// +// Description: +// Initializes the authorization structures (security descriptor). +// +// Notes: +// This is called from RunService solely for debugging purposed +// so that it can be tested by wimply running winutil service from CLI (no SCM) +// +DWORD AuthInit() { + DWORD dwError = ERROR_SUCCESS; + int count = 0; + int crt = 0; + size_t len = 0; + LPCWSTR value = NULL; + WCHAR** tokens = NULL; + LPWSTR lpszSD = NULL; + ULONG cchSD = 0; + DWORD dwBufferSize = 0; + int allowedCount = 0; + PSID* allowedSids = NULL; + + + dwError = GetConfigValue( + wsceConfigRelativePath, + NM_WSCE_ALLOWED, &len, &value); + CHECK_SVC_STATUS_DONE(dwError, L"GetConfigValue"); + + if (0 == len) { + dwError = ERROR_BAD_CONFIGURATION; + CHECK_SVC_STATUS_DONE(dwError, NM_WSCE_ALLOWED); + } + + dwError = SplitStringIgnoreSpaceW(len, value, L',', &count, &tokens); + CHECK_SVC_STATUS_DONE(dwError, L"SplitStringIgnoreSpaceW"); + + allowedSids = (PSID*) LocalAlloc(LPTR, sizeof(PSID) * count); + if (NULL == allowedSids) { + dwError = ERROR_OUTOFMEMORY; + CHECK_SVC_STATUS_DONE(dwError, L"LocalAlloc"); + } + + for (crt = 0; crt < count; ++crt) { + dwError = GetSidFromAcctNameW(tokens[crt], &allowedSids[crt]); + CHECK_SVC_STATUS_DONE(dwError, L"GetSidFromAcctNameW"); + } + + allowedCount = count; + + dwError = BuildServiceSecurityDescriptor(SERVICE_ACCESS_MASK, + allowedCount, allowedSids, 0, NULL, NULL, &pAllowedSD); + CHECK_SVC_STATUS_DONE(dwError, L"BuildServiceSecurityDescriptor"); + +done: + if (lpszSD) LocalFree(lpszSD); + if (value) LocalFree(value); + if (tokens) LocalFree(tokens); + return dwError; +} + +//---------------------------------------------------------------------------- +// Function: RpcInit +// +// Description: +// Initializes the RPC infrastructure and starts the RPC listenner. +// +DWORD RpcInit() { + RPC_STATUS status; + DWORD dwError; + + status = RpcServerUseProtseqIf(SVCBINDING, + RPC_C_LISTEN_MAX_CALLS_DEFAULT, + HadoopWinutilSvc_v1_0_s_ifspec, + NULL); + if (RPC_S_OK != status) { + ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, + status, L"RpcServerUseProtseqIf"); + SvcError(status); + dwError = status; + goto done; + } + + status = RpcServerRegisterIfEx(HadoopWinutilSvc_v1_0_s_ifspec, + NULL, // MgrTypeUuid + NULL, // MgrEpv + RPC_IF_ALLOW_LOCAL_ONLY, // Flags + RPC_C_LISTEN_MAX_CALLS_DEFAULT, // Max calls + RpcAuthorizeCallback); // Auth callback + + if (RPC_S_OK != status) { + ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, + status, L"RpcServerRegisterIfEx"); + SvcError(status); + dwError = status; + goto done; + } + + status = RpcServerListen(1, RPC_C_LISTEN_MAX_CALLS_DEFAULT, TRUE); + if (RPC_S_ALREADY_LISTENING == status) { + ReportSvcCheckError(EVENTLOG_WARNING_TYPE, SERVICE_CATEGORY, + status, L"RpcServerListen"); + } + else if (RPC_S_OK != status) { + ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, + status, L"RpcServerListen"); + SvcError(status); + dwError = status; + goto done; + } + + isListenning = TRUE; + + ReportSvcMessage(EVENTLOG_INFORMATION_TYPE, SERVICE_CATEGORY, + MSG_RPC_SERVICE_HAS_STARTED); + +done: + return dwError; +} + +//---------------------------------------------------------------------------- +// Function: RpcStop +// +// Description: +// Tears down the RPC infrastructure and stops the RPC listenner. +// +VOID RpcStop() { + RPC_STATUS status; + + if (isListenning) { + + status = RpcMgmtStopServerListening(NULL); + isListenning = FALSE; + + if (RPC_S_OK != status) { + ReportSvcCheckError(EVENTLOG_WARNING_TYPE, SERVICE_CATEGORY, + status, L"RpcMgmtStopServerListening"); + } + + ReportSvcMessage(EVENTLOG_INFORMATION_TYPE, SERVICE_CATEGORY, + MSG_RPC_SERVICE_HAS_STOPPED); + } +} + +//---------------------------------------------------------------------------- +// Function: CleanupHandles +// +// Description: +// Cleans up the global service handles. +// +VOID CleanupHandles() { + if (INVALID_HANDLE_VALUE != ghWaitObject) { + UnregisterWait(ghWaitObject); + ghWaitObject = INVALID_HANDLE_VALUE; + } + if (INVALID_HANDLE_VALUE != ghSvcStopEvent) { + CloseHandle(ghSvcStopEvent); + ghSvcStopEvent = INVALID_HANDLE_VALUE; + } + if (INVALID_HANDLE_VALUE != ghEventLog) { + DeregisterEventSource(ghEventLog); + ghEventLog = INVALID_HANDLE_VALUE; + } +} + +//---------------------------------------------------------------------------- +// Function: SvcError +// +// Description: +// Aborts the startup sequence. Reports error, stops RPC, cleans up globals. +// +VOID SvcError(DWORD dwError) { + RpcStop(); + CleanupHandles(); + ReportSvcStatus( SERVICE_STOPPED, dwError, 0 ); +} + +//---------------------------------------------------------------------------- +// Function: SvcShutdown +// +// Description: +// Callback when the shutdown event is signaled. Stops RPC, cleans up globals. +// +VOID CALLBACK SvcShutdown( + _In_ PVOID lpParameter, + _In_ BOOLEAN TimerOrWaitFired) { + RpcStop(); + CleanupHandles(); + ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 ); +} + +//---------------------------------------------------------------------------- +// Function: SvcCtrlHandler +// +// Description: +// Callback from SCM for for service events (signals). +// +// Notes: +// Shutdown is indirect, we set her the STOP_PENDING state and signal the stop event. +// Signaling the event invokes SvcShutdown which completes the shutdown. +// This two staged approach allows the SCM handler to complete fast, +// not blocking the SCM big fat global lock. +// +VOID WINAPI SvcCtrlHandler( DWORD dwCtrl ) +{ + // Handle the requested control code. + + switch(dwCtrl) + { + case SERVICE_CONTROL_STOP: + ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); + + // Signal the service to stop. + SetEvent(ghSvcStopEvent); + + return; + + default: + break; + } + +} + +//---------------------------------------------------------------------------- +// Function: ReportSvcStatus +// +// Description: +// Updates the service status with the SCM. +// +VOID ReportSvcStatus( DWORD dwCurrentState, + DWORD dwWin32ExitCode, + DWORD dwWaitHint) +{ + static DWORD dwCheckPoint = 1; + DWORD dwError; + + // Fill in the SERVICE_STATUS structure. + + gSvcStatus.dwCurrentState = dwCurrentState; + gSvcStatus.dwWin32ExitCode = dwWin32ExitCode; + gSvcStatus.dwWaitHint = dwWaitHint; + + if (dwCurrentState == SERVICE_START_PENDING) + gSvcStatus.dwControlsAccepted = 0; + else gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; + + if ( (dwCurrentState == SERVICE_RUNNING) || + (dwCurrentState == SERVICE_STOPPED) ) + gSvcStatus.dwCheckPoint = 0; + else gSvcStatus.dwCheckPoint = dwCheckPoint++; + + // Report the status of the service to the SCM. + if (!SetServiceStatus( gSvcStatusHandle, &gSvcStatus)) { + dwError = GetLastError(); + ReportSvcCheckError(EVENTLOG_WARNING_TYPE, SERVICE_CATEGORY, + dwError, L"SetServiceStatus"); + }; +} + +//---------------------------------------------------------------------------- +// Function: WinutilsCreateProcessAsUser +// +// Description: +// The RPC midl declared function implementation +// +// Returns: +// ERROR_SUCCESS: On success +// Error code otherwise: otherwise +// +// Notes: +// This is the entry point when the NodeManager does the RPC call +// Note that the RPC call does not do any S4U work. Is simply spawns (suspended) wintutils +// using the right command line and the handles over the spwaned process to the NM +// The actual S4U work occurs in the spawned process, run and monitored by the NM +// +error_status_t WinutilsCreateProcessAsUser( + /* [in] */ handle_t IDL_handle, + /* [in] */ int nmPid, + /* [in] */ CREATE_PROCESS_REQUEST *request, + /* [out] */ CREATE_PROCESS_RESPONSE **response) { + + DWORD dwError = ERROR_SUCCESS; + LPCWSTR inserts[] = {request->cwd, request->jobName, request->user, request->pidFile, request->cmdLine, NULL}; + WCHAR winutilsPath[MAX_PATH]; + WCHAR fullCmdLine[32768]; + HANDLE taskStdInRd = INVALID_HANDLE_VALUE, taskStdInWr = INVALID_HANDLE_VALUE, + taskStdOutRd = INVALID_HANDLE_VALUE, taskStdOutWr = INVALID_HANDLE_VALUE, + taskStdErrRd = INVALID_HANDLE_VALUE, taskStdErrWr = INVALID_HANDLE_VALUE, + hNmProcess = INVALID_HANDLE_VALUE, + hDuplicateProcess = INVALID_HANDLE_VALUE, + hDuplicateThread = INVALID_HANDLE_VALUE, + hDuplicateStdIn = INVALID_HANDLE_VALUE, + hDuplicateStdOut = INVALID_HANDLE_VALUE, + hDuplicateStdErr = INVALID_HANDLE_VALUE, + hSelfProcess = INVALID_HANDLE_VALUE, + hJob = INVALID_HANDLE_VALUE; + BOOL fMustCleanupProcess = FALSE; + + HRESULT hr; + STARTUPINFO si; + PROCESS_INFORMATION pi; + SECURITY_ATTRIBUTES saTaskStdInOutErr; + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory( &pi, sizeof(pi) ); + pi.hProcess = INVALID_HANDLE_VALUE; + pi.hThread = INVALID_HANDLE_VALUE; + ZeroMemory( &saTaskStdInOutErr, sizeof(saTaskStdInOutErr)); + + + if (gJobName) { + hJob = OpenJobObject(JOB_OBJECT_ASSIGN_PROCESS, FALSE, gJobName); + if (!hJob) { + dwError = GetLastError(); + ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, + dwError, L"OpenJobObject"); + goto done; + } + } + + + // NB: GetCurrentProcess returns a pseudo-handle that just so happens + // has the value -1, ie. INVALID_HANDLE_VALUE. It cannot fail. + // + hSelfProcess = GetCurrentProcess(); + + hNmProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, nmPid); + if (NULL == hNmProcess) { + dwError = GetLastError(); + ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, + dwError, L"OpenProcess"); + goto done; + } + + GetModuleFileName(NULL, winutilsPath, sizeof(winutilsPath)/sizeof(WCHAR)); + dwError = GetLastError(); // Always check after GetModuleFileName for ERROR_INSSUFICIENT_BUFFER + if (dwError) { + ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, + dwError, L"GetModuleFileName"); + goto done; + } + + // NB. We can call CreateProcess("wintuls","task create ...") or we can call + // CreateProcess(NULL, "winutils task create"). Only the second form passes "task" as + // argv[1], as expected by main. First form passes "task" as argv[0] and main fails. + + hr = StringCbPrintf(fullCmdLine, sizeof(fullCmdLine), L"\"%s\" task createAsUser %ls %ls %ls %ls", + winutilsPath, + request->jobName, request->user, request->pidFile, request->cmdLine); + if (FAILED(hr)) { + ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, + hr, L"StringCbPrintf:fullCmdLine"); + goto done; + } + + LogDebugMessage(L"[%ls]: %ls %ls\n", request->cwd, winutilsPath, fullCmdLine); + + // stdin/stdout/stderr redirection is handled here + // We create 3 anonymous named pipes. + // Security attributes are required so that the handles can be inherited. + // We assign one end of the pipe to the process (stdin gets a read end, stdout gets a write end) + // We then duplicate the other end in the NM process, and we close our own handle + // Finally we return the duplicate handle values to the NM + // The NM will attach Java file dscriptors to the duplicated handles and + // read/write them as ordinary Java InputStream/OutputStream objects + + si.dwFlags |= STARTF_USESTDHANDLES; + + saTaskStdInOutErr.nLength = sizeof(SECURITY_ATTRIBUTES); + saTaskStdInOutErr.bInheritHandle = TRUE; + saTaskStdInOutErr.lpSecurityDescriptor = NULL; + + if (!CreatePipe(&taskStdInRd, &taskStdInWr, &saTaskStdInOutErr, 0)) { + dwError = GetLastError(); + goto done; + } + if (!SetHandleInformation(taskStdInWr, HANDLE_FLAG_INHERIT, FALSE)) { + dwError = GetLastError(); + goto done; + } + si.hStdInput = taskStdInRd; + + if (!CreatePipe(&taskStdOutRd, &taskStdOutWr, &saTaskStdInOutErr, 0)) { + dwError = GetLastError(); + goto done; + } + if (!SetHandleInformation(taskStdOutRd, HANDLE_FLAG_INHERIT, FALSE)) { + dwError = GetLastError(); + goto done; + } + si.hStdOutput = taskStdOutWr; + + if (!CreatePipe(&taskStdErrRd, &taskStdErrWr, &saTaskStdInOutErr, 0)) { + dwError = GetLastError(); + goto done; + } + if (!SetHandleInformation(taskStdErrRd, HANDLE_FLAG_INHERIT, FALSE)) { + dwError = GetLastError(); + goto done; + } + si.hStdError = taskStdErrWr; + + if (!CreateProcess( + NULL, // lpApplicationName, + fullCmdLine, // lpCommandLine, + NULL, // lpProcessAttributes, + NULL, // lpThreadAttributes, + TRUE, // bInheritHandles, + CREATE_SUSPENDED, // dwCreationFlags, + NULL, // lpEnvironment, + request->cwd, // lpCurrentDirectory, + &si, // lpStartupInfo + &pi)) { // lpProcessInformation + + dwError = GetLastError(); + ReportSvcCheckError(EVENTLOG_ERROR_TYPE, SERVICE_CATEGORY, + dwError, L"CreateProcess"); + goto done; + } + + fMustCleanupProcess = TRUE; + + LogDebugMessage(L"CreateProcess: pid:%x\n", pi.dwProcessId); + + if (INVALID_HANDLE_VALUE != hJob) { + if (!AssignProcessToJobObject(hJob, pi.hProcess)) { + dwError = GetLastError(); + goto done; + } + } + + // Grant full access to the container user on the 'winutils task createAsUser ...' helper process + dwError = AddNodeManagerAndUserACEsToObject(pi.hProcess, request->user, PROCESS_ALL_ACCESS); + if (dwError) { + LogDebugMessage(L"failed: AddNodeManagerAndUserACEsToObject\n"); + goto done; + } + + if (!DuplicateHandle(hSelfProcess, pi.hProcess, hNmProcess, + &hDuplicateProcess, 0, FALSE, DUPLICATE_SAME_ACCESS)) { + dwError = GetLastError(); + LogDebugMessage(L"failed: pi.hProcess\n"); + goto done; + } + + if (!DuplicateHandle(hSelfProcess, pi.hThread, hNmProcess, + &hDuplicateThread, 0, FALSE, DUPLICATE_SAME_ACCESS)) { + dwError = GetLastError(); + LogDebugMessage(L"failed: pi.hThread\n"); + goto done; + } + + if (!DuplicateHandle(hSelfProcess, taskStdInWr, hNmProcess, + &hDuplicateStdIn, 0, FALSE, DUPLICATE_SAME_ACCESS)) { + dwError = GetLastError(); + LogDebugMessage(L"failed: taskStdInWr\n"); + goto done; + } + + if (!DuplicateHandle(hSelfProcess, taskStdOutRd, hNmProcess, + &hDuplicateStdOut, 0, FALSE, DUPLICATE_SAME_ACCESS)) { + dwError = GetLastError(); + LogDebugMessage(L"failed: taskStdOutRd\n"); + goto done; + } + + if (!DuplicateHandle(hSelfProcess, taskStdErrRd, hNmProcess, + &hDuplicateStdErr, 0, FALSE, DUPLICATE_SAME_ACCESS)) { + dwError = GetLastError(); + LogDebugMessage(L"failed: taskStdErrRd\n"); + goto done; + } + + *response = (CREATE_PROCESS_RESPONSE*) MIDL_user_allocate(sizeof(CREATE_PROCESS_RESPONSE)); + if (NULL == *response) { + dwError = ERROR_OUTOFMEMORY; + LogDebugMessage(L"Failed to allocate CREATE_PROCESS_RESPONSE* response\n"); + goto done; + } + + // We're now transfering ownership of the duplicated handles to the caller + // If the RPC call fails *after* this point the handles are leaked inside the NM process + // Note that there are no more API calls, only assignments. A failure could occur only if + // foced (process kill) or hardware error (faulty memory, processort bit flip etc). + + (*response)->hProcess = hDuplicateProcess; + (*response)->hThread = hDuplicateThread; + (*response)->hStdIn = hDuplicateStdIn; + (*response)->hStdOut = hDuplicateStdOut; + (*response)->hStdErr = hDuplicateStdErr; + + fMustCleanupProcess = FALSE; + +done: + + if (fMustCleanupProcess) { + LogDebugMessage(L"Cleaning process: %d due to error:%d\n", pi.dwProcessId, dwError); + TerminateProcess(pi.hProcess, EXIT_FAILURE); + + // cleanup the duplicate handles inside the NM. + + if (INVALID_HANDLE_VALUE != hDuplicateProcess) { + DuplicateHandle(hNmProcess, hDuplicateProcess, NULL, NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + } + if (INVALID_HANDLE_VALUE != hDuplicateThread) { + DuplicateHandle(hNmProcess, hDuplicateThread, NULL, NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + } + if (INVALID_HANDLE_VALUE != hDuplicateStdIn) { + DuplicateHandle(hNmProcess, hDuplicateStdIn, NULL, NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + } + if (INVALID_HANDLE_VALUE != hDuplicateStdOut) { + DuplicateHandle(hNmProcess, hDuplicateStdOut, NULL, NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + } + if (INVALID_HANDLE_VALUE != hDuplicateStdErr) { + DuplicateHandle(hNmProcess, hDuplicateStdErr, NULL, NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + } + } + + if (INVALID_HANDLE_VALUE != hSelfProcess) CloseHandle(hSelfProcess); + if (INVALID_HANDLE_VALUE != hNmProcess) CloseHandle(hNmProcess); + if (INVALID_HANDLE_VALUE != taskStdInRd) CloseHandle(taskStdInRd); + if (INVALID_HANDLE_VALUE != taskStdInWr) CloseHandle(taskStdInWr); + if (INVALID_HANDLE_VALUE != taskStdOutRd) CloseHandle(taskStdOutRd); + if (INVALID_HANDLE_VALUE != taskStdOutWr) CloseHandle(taskStdOutWr); + if (INVALID_HANDLE_VALUE != taskStdErrRd) CloseHandle(taskStdErrRd); + if (INVALID_HANDLE_VALUE != taskStdErrWr) CloseHandle(taskStdErrWr); + + + // This is closing our own process/thread handles. + // If the transfer was succesfull the NM has its own duplicates (if any) + if (INVALID_HANDLE_VALUE != pi.hThread) CloseHandle(pi.hThread); + if (INVALID_HANDLE_VALUE != pi.hProcess) CloseHandle(pi.hProcess); + + if (hJob) CloseHandle(hJob); + + return dwError; +} + +error_status_t WinutilsCreateFile( + /* [in] */ handle_t IDL_handle, + /* [in] */ int nm_pid, + /* [in] */ CREATEFILE_REQUEST *request, + /* [out] */ CREATEFILE_RESPONSE **response) { + + DWORD dwError = ERROR_SUCCESS; + + HANDLE hNmProcess = INVALID_HANDLE_VALUE, + hFile = INVALID_HANDLE_VALUE, + hDuplicateFile = INVALID_HANDLE_VALUE, + hSelfProcess = GetCurrentProcess(); + + SECURITY_ATTRIBUTES saFile; + + ZeroMemory( &saFile, sizeof(saFile)); + + dwError = ValidateLocalPath(request->path); + CHECK_SVC_STATUS_DONE(dwError,L"ValidateLocalPath request->path"); + + saFile.nLength = sizeof(SECURITY_ATTRIBUTES); + saFile.bInheritHandle = TRUE; + saFile.lpSecurityDescriptor = NULL; + + hFile = CreateFile( + request->path, + request->desiredAccess, + request->shareMode, + &saFile, + request->creationDisposition, + request->flags, + NULL); // hTemplate + if (INVALID_HANDLE_VALUE == hFile) { + dwError = GetLastError(); + goto done; + } + + hNmProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, nm_pid); + if (NULL == hNmProcess) { + dwError = GetLastError(); + goto done; + } + + if (!DuplicateHandle(hSelfProcess, hFile, + hNmProcess, &hDuplicateFile, + 0, FALSE, DUPLICATE_SAME_ACCESS)) { + dwError = GetLastError(); + goto done; + } + + *response = (CREATEFILE_RESPONSE*) MIDL_user_allocate(sizeof(CREATEFILE_RESPONSE)); + if (NULL == *response) { + dwError = ERROR_OUTOFMEMORY; + goto done; + } + + (*response)->hFile = hDuplicateFile; + hDuplicateFile = INVALID_HANDLE_VALUE; + +done: + + if (INVALID_HANDLE_VALUE != hFile) CloseHandle(hFile); + if (INVALID_HANDLE_VALUE != hDuplicateFile) { + DuplicateHandle(hNmProcess, hDuplicateFile, NULL, NULL, 0, FALSE, DUPLICATE_CLOSE_SOURCE); + } + if (INVALID_HANDLE_VALUE != hNmProcess) CloseHandle(hNmProcess); + + LogDebugMessage(L"WinutilsCreateFile: %s %d, %d, %d, %d: %d", + request->path, + request->desiredAccess, + request->shareMode, + request->creationDisposition, + request->flags, + dwError); + + return dwError; +} + +error_status_t WinutilsKillTask( + /* [in] */ handle_t IDL_handle, + /* [in] */ KILLTASK_REQUEST *request) { + DWORD dwError = ERROR_SUCCESS; + HRESULT hr; + WCHAR bufferName[MAX_PATH]; + + dwError = GetSecureJobObjectName(request->taskName, MAX_PATH, bufferName); + CHECK_SVC_STATUS_DONE(dwError, L"GetSecureJobObjectName"); + + dwError = KillTask(bufferName); + + if (ERROR_ACCESS_DENIED == dwError) { + // This process runs as LocalSystem with debug privilege enabled + // The job has a security descriptor that explictly grants JOB_OBJECT_ALL_ACCESS to us + // If we get ACCESS DENIED it means the job is being unwound + dwError = ERROR_SUCCESS; + } + +done: + LogDebugMessage(L"WinutilsKillTask: %s :%d\n", bufferName, dwError); + return dwError; +} + + +error_status_t WinutilsDeletePath( + /* [in] */ handle_t IDL_handle, + /* [in] */ DELETEPATH_REQUEST *request, + /* [out] */ DELETEPATH_RESPONSE **response) { + + DWORD dwError = ERROR_SUCCESS; + BOOL deleted = FALSE; + + dwError = ValidateLocalPath(request->path); + CHECK_SVC_STATUS_DONE(dwError,L"ValidateLocalPath request->path"); + + switch(request->type) { + case PATH_IS_DIR: + deleted = RemoveDirectory(request->path); + if (!deleted) { + LogDebugMessage(L"Error %d deleting directory %s\n", GetLastError(), request->path); + } + break; + case PATH_IS_FILE: + deleted = DeleteFile(request->path); + if (!deleted) { + LogDebugMessage(L"Error %d deleting file %s\n", GetLastError(), request->path); + } + break; + default: + dwError = ERROR_BAD_ARGUMENTS; + CHECK_SVC_STATUS_DONE(dwError, L"request->operation"); + } + + *response = (DELETEPATH_RESPONSE*) MIDL_user_allocate(sizeof(DELETEPATH_RESPONSE)); + if (NULL == *response) { + dwError = ERROR_OUTOFMEMORY; + CHECK_SVC_STATUS_DONE(dwError, L"MIDL_user_allocate"); + } + + (*response)->deleted = deleted; + +done: + + LogDebugMessage(L"WinutilsDeletePath: %s %d: %d %d", + request->path, + request->type, + deleted, + dwError); + + return dwError; +} + +error_status_t WinutilsMkDir( + /* [in] */ handle_t IDL_handle, + /* [in] */ MKDIR_REQUEST *request) { + DWORD dwError = ERROR_SUCCESS; + + dwError = ValidateLocalPath(request->filePath); + CHECK_SVC_STATUS_DONE(dwError,L"ValidateLocalPath request->filePath"); + + if (!CreateDirectory(request->filePath, NULL)) { + dwError = GetLastError(); + CHECK_SVC_STATUS_DONE(dwError, L"CreateDirectory"); + } + +done: + LogDebugMessage(L"WinutilsMkDir: %s :%d\n", request->filePath, dwError); + return dwError; +} + +error_status_t WinutilsChown( + /* [in] */ handle_t IDL_handle, + /* [in] */ CHOWN_REQUEST *request) { + DWORD dwError = ERROR_SUCCESS; + + dwError = ValidateLocalPath(request->filePath); + CHECK_SVC_STATUS_DONE(dwError,L"ValidateLocalPath request->filePath"); + + dwError = ChownImpl(request->ownerName, request->groupName, request->filePath); + CHECK_SVC_STATUS_DONE(dwError, L"ChownImpl"); + +done: + LogDebugMessage(L"WinutilsChown: %s %s %s :%d\n", + request->ownerName, request->groupName, request->filePath, dwError); + return dwError; +} + +error_status_t WinutilsChmod( + /* [in] */ handle_t IDL_handle, + /* [in] */ CHMOD_REQUEST *request) { + DWORD dwError = ERROR_SUCCESS; + + dwError = ValidateLocalPath(request->filePath); + CHECK_SVC_STATUS_DONE(dwError,L"ValidateLocalPath request->filePath"); + + dwError = ChangeFileModeByMask(request->filePath, request->mode); + CHECK_SVC_STATUS_DONE(dwError, L"ChangeFileModeByMask"); + +done: + LogDebugMessage(L"WinutilsChmod: %s %o :%d\n", + request->filePath, request->mode, dwError); + return dwError; +} + +error_status_t WinutilsMoveFile( + /* [in] */ handle_t IDL_handle, + /* [in] */ MOVEFILE_REQUEST *request) { + DWORD dwError = ERROR_SUCCESS; + DWORD flags = 0; + + dwError = ValidateLocalPath(request->sourcePath); + CHECK_SVC_STATUS_DONE(dwError,L"ValidateLocalPath request->sourcePath"); + + dwError = ValidateLocalPath(request->destinationPath); + CHECK_SVC_STATUS_DONE(dwError,L"ValidateLocalPath request->destinationPath"); + + switch (request->operation) { + case MOVE_FILE: + flags |= MOVEFILE_COPY_ALLOWED; + if (request->replaceExisting) flags |= MOVEFILE_REPLACE_EXISTING; + if (!MoveFileEx(request->sourcePath, request->destinationPath, flags)) { + dwError = GetLastError(); + CHECK_SVC_STATUS_DONE(dwError, L"MoveFileEx"); + } + break; + case COPY_FILE: + if (!request->replaceExisting) flags |= COPY_FILE_FAIL_IF_EXISTS; + if (!CopyFileEx(request->sourcePath, request->destinationPath, + NULL, // lpProgressRoutine + NULL, // lpData + NULL, // pbCancel + flags)) { + dwError = GetLastError(); + CHECK_SVC_STATUS_DONE(dwError, L"CopyFileEx"); + } + break; + default: + dwError = ERROR_BAD_ARGUMENTS; + CHECK_SVC_STATUS_DONE(dwError, L"request->operation"); + } + +done: + LogDebugMessage(L"WinutilsMoveFile: %d: %s %s :%d\n", + request->operation, request->sourcePath, request->destinationPath, dwError); + return dwError; +} + + +//---------------------------------------------------------------------------- +// Function: ServiceUsage +// +// Description: +// Prints the CLI arguments for service command. +// +void ServiceUsage() +{ + fwprintf(stdout, L"\ + Usage: service\n\ + Starts the nodemanager Windows Secure Container Executor helper service.\n\ + The service must run as a high privileged account (LocalSystem)\n\ + and is used by the nodemanager WSCE to spawn secure containers on Windows.\n"); +} + + diff --git a/hadoop-common-project/hadoop-common/src/main/winutils/task.c b/hadoop-common-project/hadoop-common/src/main/winutils/task.c index 783f162322b..f0fc19a7543 100644 --- a/hadoop-common-project/hadoop-common/src/main/winutils/task.c +++ b/hadoop-common-project/hadoop-common/src/main/winutils/task.c @@ -18,29 +18,32 @@ #include "winutils.h" #include #include -#include +#include +#include +#include #define PSAPI_VERSION 1 #pragma comment(lib, "psapi.lib") -#define ERROR_TASK_NOT_ALIVE 1 +#define NM_WSCE_IMPERSONATE_ALLOWED L"yarn.nodemanager.windows-secure-container-executor.impersonate.allowed" +#define NM_WSCE_IMPERSONATE_DENIED L"yarn.nodemanager.windows-secure-container-executor.impersonate.denied" -// This exit code for killed processes is compatible with Unix, where a killed -// process exits with 128 + signal. For SIGKILL, this would be 128 + 9 = 137. -#define KILLED_PROCESS_EXIT_CODE 137 +// The S4U impersonation access check mask. Arbitrary value (we use 1 for the service access check) +#define SERVICE_IMPERSONATE_MASK 0x00000002 + + +// Name for tracking this logon process when registering with LSA +static const char *LOGON_PROCESS_NAME="Hadoop Container Executor"; +// Name for token source, must be less or eq to TOKEN_SOURCE_LENGTH (currently 8) chars +static const char *TOKEN_SOURCE_NAME = "HadoopEx"; -// Name for tracking this logon process when registering with LSA -static const char *LOGON_PROCESS_NAME="Hadoop Container Executor"; -// Name for token source, must be less or eq to TOKEN_SOURCE_LENGTH (currently 8) chars -static const char *TOKEN_SOURCE_NAME = "HadoopEx"; - // List of different task related command line options supported by // winutils. typedef enum TaskCommandOptionType { TaskInvalid, TaskCreate, - TaskCreateAsUser, + TaskCreateAsUser, TaskIsAlive, TaskKill, TaskProcessList @@ -93,53 +96,524 @@ static BOOL ParseCommandLine(__in int argc, } } - if (argc >= 6) { - if (wcscmp(argv[1], L"createAsUser") == 0) - { - *command = TaskCreateAsUser; - return TRUE; - } - } - + if (argc >= 6) { + if (wcscmp(argv[1], L"createAsUser") == 0) + { + *command = TaskCreateAsUser; + return TRUE; + } + } + return FALSE; } + //---------------------------------------------------------------------------- -// Function: CreateTaskImpl +// Function: BuildImpersonateSecurityDescriptor +// +// Description: +// Builds the security descriptor for the S4U impersonation permissions +// This describes what users can be impersonated and what not +// +// Returns: +// ERROR_SUCCESS: On success +// GetLastError: otherwise +// +DWORD BuildImpersonateSecurityDescriptor(__out PSECURITY_DESCRIPTOR* ppSD) { + DWORD dwError = ERROR_SUCCESS; + size_t countAllowed = 0; + PSID* allowedSids = NULL; + size_t countDenied = 0; + PSID* deniedSids = NULL; + LPCWSTR value = NULL; + WCHAR** tokens = NULL; + size_t len = 0; + size_t count = 0; + int crt = 0; + PSECURITY_DESCRIPTOR pSD = NULL; + + dwError = GetConfigValue(wsceConfigRelativePath, NM_WSCE_IMPERSONATE_ALLOWED, &len, &value); + if (dwError) { + ReportErrorCode(L"GetConfigValue:1", dwError); + goto done; + } + + if (0 == len) { + dwError = ERROR_BAD_CONFIGURATION; + ReportErrorCode(L"GetConfigValue:2", dwError); + goto done; + } + + dwError = SplitStringIgnoreSpaceW(len, value, L',', &count, &tokens); + if (dwError) { + ReportErrorCode(L"SplitStringIgnoreSpaceW:1", dwError); + goto done; + } + + allowedSids = LocalAlloc(LPTR, sizeof(PSID) * count); + if (NULL == allowedSids) { + dwError = GetLastError(); + ReportErrorCode(L"LocalAlloc:1", dwError); + goto done; + } + + for(crt = 0; crt < count; ++crt) { + dwError = GetSidFromAcctNameW(tokens[crt], &allowedSids[crt]); + if (dwError) { + ReportErrorCode(L"GetSidFromAcctNameW:1", dwError); + goto done; + } + } + countAllowed = count; + + LocalFree(tokens); + tokens = NULL; + + LocalFree(value); + value = NULL; + + dwError = GetConfigValue(wsceConfigRelativePath, NM_WSCE_IMPERSONATE_DENIED, &len, &value); + if (dwError) { + ReportErrorCode(L"GetConfigValue:3", dwError); + goto done; + } + + if (0 != len) { + dwError = SplitStringIgnoreSpaceW(len, value, L',', &count, &tokens); + if (dwError) { + ReportErrorCode(L"SplitStringIgnoreSpaceW:2", dwError); + goto done; + } + + deniedSids = LocalAlloc(LPTR, sizeof(PSID) * count); + if (NULL == allowedSids) { + dwError = GetLastError(); + ReportErrorCode(L"LocalAlloc:2", dwError); + goto done; + } + + for(crt = 0; crt < count; ++crt) { + dwError = GetSidFromAcctNameW(tokens[crt], &deniedSids[crt]); + if (dwError) { + ReportErrorCode(L"GetSidFromAcctNameW:2", dwError); + goto done; + } + } + countDenied = count; + } + + dwError = BuildServiceSecurityDescriptor( + SERVICE_IMPERSONATE_MASK, + countAllowed, allowedSids, + countDenied, deniedSids, + NULL, + &pSD); + + if (dwError) { + ReportErrorCode(L"BuildServiceSecurityDescriptor", dwError); + goto done; + } + + *ppSD = pSD; + pSD = NULL; + +done: + if (pSD) LocalFree(pSD); + if (tokens) LocalFree(tokens); + if (allowedSids) LocalFree(allowedSids); + if (deniedSids) LocalFree(deniedSids); + return dwError; +} + +//---------------------------------------------------------------------------- +// Function: AddNodeManagerAndUserACEsToObject +// +// Description: +// Adds ACEs to grant NM and user the provided access mask over a given handle +// +// Returns: +// ERROR_SUCCESS: on success +// +DWORD AddNodeManagerAndUserACEsToObject( + __in HANDLE hObject, + __in LPWSTR user, + __in ACCESS_MASK accessMask) { + + DWORD dwError = ERROR_SUCCESS; + int countTokens = 0; + size_t len = 0; + LPCWSTR value = NULL; + WCHAR** tokens = NULL; + int crt = 0; + PACL pDacl = NULL; + PSECURITY_DESCRIPTOR psdProcess = NULL; + LPSTR lpszOldDacl = NULL, lpszNewDacl = NULL; + ULONG daclLen = 0; + PACL pNewDacl = NULL; + ACL_SIZE_INFORMATION si; + DWORD dwNewAclSize = 0; + PACE_HEADER pTempAce = NULL; + BYTE sidTemp[SECURITY_MAX_SID_SIZE]; + DWORD cbSid = SECURITY_MAX_SID_SIZE; + PSID tokenSid = NULL; + // These hard-coded SIDs are allways added + WELL_KNOWN_SID_TYPE forcesSidTypes[] = { + WinLocalSystemSid, + WinBuiltinAdministratorsSid}; + BOOL logSDs = IsDebuggerPresent(); // Check only once to avoid attach-while-running + + + dwError = GetSecurityInfo(hObject, + SE_KERNEL_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, + NULL, + &pDacl, + NULL, + &psdProcess); + if (dwError) { + ReportErrorCode(L"GetSecurityInfo", dwError); + goto done; + } + + // This is debug only output for troubleshooting + if (logSDs) { + if (!ConvertSecurityDescriptorToStringSecurityDescriptor( + psdProcess, + SDDL_REVISION_1, + DACL_SECURITY_INFORMATION, + &lpszOldDacl, + &daclLen)) { + dwError = GetLastError(); + ReportErrorCode(L"ConvertSecurityDescriptorToStringSecurityDescriptor", dwError); + goto done; + } + } + + ZeroMemory(&si, sizeof(si)); + if (!GetAclInformation(pDacl, &si, sizeof(si), AclSizeInformation)) { + dwError = GetLastError(); + ReportErrorCode(L"GetAclInformation", dwError); + goto done; + } + + dwError = GetConfigValue(wsceConfigRelativePath, NM_WSCE_ALLOWED, &len, &value); + if (ERROR_SUCCESS != dwError) { + ReportErrorCode(L"GetConfigValue", dwError); + goto done; + } + + if (0 == len) { + dwError = ERROR_BAD_CONFIGURATION; + ReportErrorCode(L"GetConfigValue", dwError); + goto done; + } + + dwError = SplitStringIgnoreSpaceW(len, value, L',', &countTokens, &tokens); + if (ERROR_SUCCESS != dwError) { + ReportErrorCode(L"SplitStringIgnoreSpaceW", dwError); + goto done; + } + + // We're gonna add 1 ACE for each token found, +1 for user and +1 for each forcesSidTypes[] + // ACCESS_ALLOWED_ACE struct contains the first DWORD of the SID + // + dwNewAclSize = si.AclBytesInUse + + (countTokens + 1 + sizeof(forcesSidTypes)/sizeof(forcesSidTypes[0])) * + (sizeof(ACCESS_ALLOWED_ACE) + SECURITY_MAX_SID_SIZE - sizeof(DWORD)); + + pNewDacl = (PSID) LocalAlloc(LPTR, dwNewAclSize); + if (!pNewDacl) { + dwError = ERROR_OUTOFMEMORY; + ReportErrorCode(L"LocalAlloc", dwError); + goto done; + } + + if (!InitializeAcl(pNewDacl, dwNewAclSize, ACL_REVISION)) { + dwError = ERROR_OUTOFMEMORY; + ReportErrorCode(L"InitializeAcl", dwError); + goto done; + } + + // Copy over old ACEs + for (crt = 0; crt < si.AceCount; ++crt) { + if (!GetAce(pDacl, crt, &pTempAce)) { + dwError = ERROR_OUTOFMEMORY; + ReportErrorCode(L"InitializeAcl", dwError); + goto done; + } + if (!AddAce(pNewDacl, ACL_REVISION, MAXDWORD, pTempAce, pTempAce->AceSize)) { + dwError = ERROR_OUTOFMEMORY; + ReportErrorCode(L"InitializeAcl", dwError); + goto done; + } + } + + // Add the configured allowed SIDs + for (crt = 0; crt < countTokens; ++crt) { + dwError = GetSidFromAcctNameW(tokens[crt], &tokenSid); + if (ERROR_SUCCESS != dwError) { + ReportErrorCode(L"GetSidFromAcctNameW", dwError); + goto done; + } + if (!AddAccessAllowedAceEx( + pNewDacl, + ACL_REVISION_DS, + CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, + PROCESS_ALL_ACCESS, + tokenSid)) { + dwError = GetLastError(); + ReportErrorCode(L"AddAccessAllowedAceEx:1", dwError); + goto done; + } + LocalFree(tokenSid); + tokenSid = NULL; + } + + // add the forced SIDs ACE + for (crt = 0; crt < sizeof(forcesSidTypes)/sizeof(forcesSidTypes[0]); ++crt) { + cbSid = SECURITY_MAX_SID_SIZE; + if (!CreateWellKnownSid(forcesSidTypes[crt], NULL, &sidTemp, &cbSid)) { + dwError = GetLastError(); + ReportErrorCode(L"CreateWellKnownSid", dwError); + goto done; + } + if (!AddAccessAllowedAceEx( + pNewDacl, + ACL_REVISION_DS, + CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, + accessMask, + (PSID) sidTemp)) { + dwError = GetLastError(); + ReportErrorCode(L"AddAccessAllowedAceEx:2", dwError); + goto done; + } + } + + // add the user ACE + dwError = GetSidFromAcctNameW(user, &tokenSid); + if (ERROR_SUCCESS != dwError) { + ReportErrorCode(L"GetSidFromAcctNameW:user", dwError); + goto done; + } + + if (!AddAccessAllowedAceEx( + pNewDacl, + ACL_REVISION_DS, + CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, + PROCESS_ALL_ACCESS, + tokenSid)) { + dwError = GetLastError(); + ReportErrorCode(L"AddAccessAllowedAceEx:3", dwError); + goto done; + } + + LocalFree(tokenSid); + tokenSid = NULL; + + dwError = SetSecurityInfo(hObject, + SE_KERNEL_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, + NULL, + pNewDacl, + NULL); + if (dwError) { + ReportErrorCode(L"SetSecurityInfo", dwError); + goto done; + } + + // This is debug only output for troubleshooting + if (logSDs) { + dwError = GetSecurityInfo(hObject, + SE_KERNEL_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, + NULL, + &pDacl, + NULL, + &psdProcess); + if (dwError) { + ReportErrorCode(L"GetSecurityInfo:2", dwError); + goto done; + } + + if (!ConvertSecurityDescriptorToStringSecurityDescriptor( + psdProcess, + SDDL_REVISION_1, + DACL_SECURITY_INFORMATION, + &lpszNewDacl, + &daclLen)) { + dwError = GetLastError(); + ReportErrorCode(L"ConvertSecurityDescriptorToStringSecurityDescriptor:2", dwError); + goto done; + } + + LogDebugMessage(L"Old DACL: %s\nNew DACL: %s\n", lpszOldDacl, lpszNewDacl); + } + +done: + if (tokenSid) LocalFree(tokenSid); + if (pNewDacl) LocalFree(pNewDacl); + if (lpszOldDacl) LocalFree(lpszOldDacl); + if (lpszNewDacl) LocalFree(lpszNewDacl); + if (psdProcess) LocalFree(psdProcess); + + return dwError; +} + +//---------------------------------------------------------------------------- +// Function: ValidateImpersonateAccessCheck +// +// Description: +// Performs the access check for S4U impersonation +// +// Returns: +// ERROR_SUCCESS: On success +// ERROR_ACCESS_DENIED, GetLastError: otherwise +// +DWORD ValidateImpersonateAccessCheck(__in HANDLE logonHandle) { + DWORD dwError = ERROR_SUCCESS; + PSECURITY_DESCRIPTOR pSD = NULL; + LUID luidUnused; + AUTHZ_ACCESS_REQUEST request; + AUTHZ_ACCESS_REPLY reply; + DWORD authError = ERROR_SUCCESS; + DWORD saclResult = 0; + ACCESS_MASK grantedMask = 0; + AUTHZ_RESOURCE_MANAGER_HANDLE hManager = NULL; + AUTHZ_CLIENT_CONTEXT_HANDLE hAuthzToken = NULL; + + ZeroMemory(&luidUnused, sizeof(luidUnused)); + ZeroMemory(&request, sizeof(request)); + ZeroMemory(&reply, sizeof(reply)); + + dwError = BuildImpersonateSecurityDescriptor(&pSD); + if (dwError) { + ReportErrorCode(L"BuildImpersonateSecurityDescriptor", dwError); + goto done; + } + + request.DesiredAccess = MAXIMUM_ALLOWED; + reply.Error = &authError; + reply.SaclEvaluationResults = &saclResult; + reply.ResultListLength = 1; + reply.GrantedAccessMask = &grantedMask; + + if (!AuthzInitializeResourceManager( + AUTHZ_RM_FLAG_NO_AUDIT, + NULL, // pfnAccessCheck + NULL, // pfnComputeDynamicGroups + NULL, // pfnFreeDynamicGroups + NULL, // szResourceManagerName + &hManager)) { + dwError = GetLastError(); + ReportErrorCode(L"AuthzInitializeResourceManager", dwError); + goto done; + } + + if (!AuthzInitializeContextFromToken( + 0, + logonHandle, + hManager, + NULL, // expiration time + luidUnused, // not used + NULL, // callback args + &hAuthzToken)) { + dwError = GetLastError(); + ReportErrorCode(L"AuthzInitializeContextFromToken", dwError); + goto done; + } + + if (!AuthzAccessCheck( + 0, + hAuthzToken, + &request, + NULL, // AuditEvent + pSD, + NULL, // OptionalSecurityDescriptorArray + 0, // OptionalSecurityDescriptorCount + &reply, + NULL // phAccessCheckResults + )) { + dwError = GetLastError(); + ReportErrorCode(L"AuthzAccessCheck", dwError); + goto done; + } + + LogDebugMessage(L"AutzAccessCheck: Error:%d sacl:%d access:%d\n", + authError, saclResult, grantedMask); + + if (authError != ERROR_SUCCESS) { + ReportErrorCode(L"AuthzAccessCheck:REPLY:1", authError); + dwError = authError; + } + else if (!(grantedMask & SERVICE_IMPERSONATE_MASK)) { + ReportErrorCode(L"AuthzAccessCheck:REPLY:2", ERROR_ACCESS_DENIED); + dwError = ERROR_ACCESS_DENIED; + } + +done: + if (hAuthzToken) AuthzFreeContext(hAuthzToken); + if (hManager) AuthzFreeResourceManager(hManager); + if (pSD) LocalFree(pSD); + return dwError; +} + +//---------------------------------------------------------------------------- +// Function: CreateTaskImpl // // Description: // Creates a task via a jobobject. Outputs the // appropriate information to stdout on success, or stderr on failure. -// logonHandle may be NULL, in this case the current logon will be utilized for the -// created process +// logonHandle may be NULL, in this case the current logon will be utilized for the +// created process // // Returns: // ERROR_SUCCESS: On success // GetLastError: otherwise -DWORD CreateTaskImpl(__in_opt HANDLE logonHandle, __in PCWSTR jobObjName,__in PWSTR cmdLine) +DWORD CreateTaskImpl(__in_opt HANDLE logonHandle, __in PCWSTR jobObjName,__in PCWSTR cmdLine, + __in LPCWSTR userName) { - DWORD dwErrorCode = ERROR_SUCCESS; + DWORD dwErrorCode = ERROR_SUCCESS; DWORD exitCode = EXIT_FAILURE; - DWORD currDirCnt = 0; + DWORD currDirCnt = 0; STARTUPINFO si; PROCESS_INFORMATION pi; HANDLE jobObject = NULL; JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 }; - void * envBlock = NULL; - BOOL createProcessResult = FALSE; - - wchar_t* curr_dir = NULL; - FILE *stream = NULL; + void * envBlock = NULL; + WCHAR secureJobNameBuffer[MAX_PATH]; + LPCWSTR secureJobName = jobObjName; + + wchar_t* curr_dir = NULL; + FILE *stream = NULL; + + if (NULL != logonHandle) { + dwErrorCode = ValidateImpersonateAccessCheck(logonHandle); + if (dwErrorCode) { + ReportErrorCode(L"ValidateImpersonateAccessCheck", dwErrorCode); + return dwErrorCode; + } + + dwErrorCode = GetSecureJobObjectName(jobObjName, MAX_PATH, secureJobNameBuffer); + if (dwErrorCode) { + ReportErrorCode(L"GetSecureJobObjectName", dwErrorCode); + return dwErrorCode; + } + secureJobName = secureJobNameBuffer; + } // Create un-inheritable job object handle and set job object to terminate // when last handle is closed. So winutils.exe invocation has the only open // job object handle. Exit of winutils.exe ensures termination of job object. // Either a clean exit of winutils or crash or external termination. - jobObject = CreateJobObject(NULL, jobObjName); - dwErrorCode = GetLastError(); - if(jobObject == NULL || dwErrorCode == ERROR_ALREADY_EXISTS) + jobObject = CreateJobObject(NULL, secureJobName); + dwErrorCode = GetLastError(); + if(jobObject == NULL || dwErrorCode == ERROR_ALREADY_EXISTS) { - return dwErrorCode; + ReportErrorCode(L"CreateJobObject", dwErrorCode); + return dwErrorCode; } jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; if(SetInformationJobObject(jobObject, @@ -147,102 +621,143 @@ DWORD CreateTaskImpl(__in_opt HANDLE logonHandle, __in PCWSTR jobObjName,__in PW &jeli, sizeof(jeli)) == 0) { - dwErrorCode = GetLastError(); + dwErrorCode = GetLastError(); + ReportErrorCode(L"SetInformationJobObject", dwErrorCode); CloseHandle(jobObject); - return dwErrorCode; - } + return dwErrorCode; + } + + dwErrorCode = AddNodeManagerAndUserACEsToObject(jobObject, userName, JOB_OBJECT_ALL_ACCESS); + if (dwErrorCode) { + ReportErrorCode(L"AddNodeManagerAndUserACEsToObject", dwErrorCode); + CloseHandle(jobObject); + return dwErrorCode; + } if(AssignProcessToJobObject(jobObject, GetCurrentProcess()) == 0) { - dwErrorCode = GetLastError(); + dwErrorCode = GetLastError(); + ReportErrorCode(L"AssignProcessToJobObject", dwErrorCode); CloseHandle(jobObject); - return dwErrorCode; + return dwErrorCode; } // the child JVM uses this env var to send the task OS process identifier // to the TaskTracker. We pass the job object name. if(SetEnvironmentVariable(L"JVM_PID", jobObjName) == 0) { - dwErrorCode = GetLastError(); - // We have to explictly Terminate, passing in the error code - // simply closing the job would kill our own process with success exit status - TerminateJobObject(jobObject, dwErrorCode); - return dwErrorCode; + dwErrorCode = GetLastError(); + ReportErrorCode(L"SetEnvironmentVariable", dwErrorCode); + // We have to explictly Terminate, passing in the error code + // simply closing the job would kill our own process with success exit status + TerminateJobObject(jobObject, dwErrorCode); + return dwErrorCode; } ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); - if( logonHandle != NULL ) { - // create user environment for this logon - if(!CreateEnvironmentBlock(&envBlock, - logonHandle, - TRUE )) { - dwErrorCode = GetLastError(); - // We have to explictly Terminate, passing in the error code - // simply closing the job would kill our own process with success exit status - TerminateJobObject(jobObject, dwErrorCode); - return dwErrorCode; - } - } - - // Get the required buffer size first - currDirCnt = GetCurrentDirectory(0, NULL); - if (0 < currDirCnt) { - curr_dir = (wchar_t*) alloca(currDirCnt * sizeof(wchar_t)); - assert(curr_dir); - currDirCnt = GetCurrentDirectory(currDirCnt, curr_dir); - } - - if (0 == currDirCnt) { - dwErrorCode = GetLastError(); - // We have to explictly Terminate, passing in the error code - // simply closing the job would kill our own process with success exit status - TerminateJobObject(jobObject, dwErrorCode); - return dwErrorCode; - } - - if (logonHandle == NULL) { - createProcessResult = CreateProcess( - NULL, // ApplicationName - cmdLine, // command line - NULL, // process security attributes - NULL, // thread security attributes - TRUE, // inherit handles - 0, // creation flags - NULL, // environment - curr_dir, // current directory - &si, // startup info - &pi); // process info - } - else { - createProcessResult = CreateProcessAsUser( - logonHandle, // logon token handle - NULL, // Application handle - cmdLine, // command line - NULL, // process security attributes - NULL, // thread security attributes - FALSE, // inherit handles - CREATE_UNICODE_ENVIRONMENT, // creation flags - envBlock, // environment - curr_dir, // current directory - &si, // startup info - &pi); // process info - } - - if (FALSE == createProcessResult) { - dwErrorCode = GetLastError(); - if( envBlock != NULL ) { - DestroyEnvironmentBlock( envBlock ); - envBlock = NULL; - } - // We have to explictly Terminate, passing in the error code - // simply closing the job would kill our own process with success exit status - TerminateJobObject(jobObject, dwErrorCode); - - // This is tehnically dead code, we cannot reach this condition - return dwErrorCode; + if( logonHandle != NULL ) { + // create user environment for this logon + if(!CreateEnvironmentBlock(&envBlock, + logonHandle, + TRUE )) { + dwErrorCode = GetLastError(); + ReportErrorCode(L"CreateEnvironmentBlock", dwErrorCode); + // We have to explictly Terminate, passing in the error code + // simply closing the job would kill our own process with success exit status + TerminateJobObject(jobObject, dwErrorCode); + return dwErrorCode; + } + } + + // Get the required buffer size first + currDirCnt = GetCurrentDirectory(0, NULL); + if (0 < currDirCnt) { + curr_dir = (wchar_t*) alloca(currDirCnt * sizeof(wchar_t)); + assert(curr_dir); + currDirCnt = GetCurrentDirectory(currDirCnt, curr_dir); + } + + if (0 == currDirCnt) { + dwErrorCode = GetLastError(); + ReportErrorCode(L"GetCurrentDirectory", dwErrorCode); + // We have to explictly Terminate, passing in the error code + // simply closing the job would kill our own process with success exit status + TerminateJobObject(jobObject, dwErrorCode); + return dwErrorCode; + } + + dwErrorCode = ERROR_SUCCESS; + + if (logonHandle == NULL) { + if (!CreateProcess( + NULL, // ApplicationName + cmdLine, // command line + NULL, // process security attributes + NULL, // thread security attributes + TRUE, // inherit handles + 0, // creation flags + NULL, // environment + curr_dir, // current directory + &si, // startup info + &pi)) { // process info + dwErrorCode = GetLastError(); + ReportErrorCode(L"CreateProcess", dwErrorCode); + } + goto create_process_done; + } + + // From here on is the secure S4U implementation for CreateProcessAsUser + + // We desire to grant process access to NM so that it can interogate process status + // and resource utilization. Passing in a security descriptor though results in the + // S4U privilege checks being done against that SD and CreateProcessAsUser fails. + // So instead we create the process suspended and then we add the desired ACEs. + // + if (!CreateProcessAsUser( + logonHandle, // logon token handle + NULL, // Application handle + cmdLine, // command line + NULL, // process security attributes + NULL, // thread security attributes + FALSE, // inherit handles + CREATE_UNICODE_ENVIRONMENT | CREATE_SUSPENDED, // creation flags + envBlock, // environment + curr_dir, // current directory + &si, // startup info + &pi)) { // process info + dwErrorCode = GetLastError(); + ReportErrorCode(L"CreateProcessAsUser", dwErrorCode); + goto create_process_done; + } + + dwErrorCode = AddNodeManagerAndUserACEsToObject(pi.hProcess, userName, PROCESS_ALL_ACCESS); + if (dwErrorCode) { + ReportErrorCode(L"AddNodeManagerAndUserACEsToObject", dwErrorCode); + goto create_process_done; + } + + if (-1 == ResumeThread(pi.hThread)) { + dwErrorCode = GetLastError(); + ReportErrorCode(L"ResumeThread", dwErrorCode); + goto create_process_done; + } + +create_process_done: + + if (dwErrorCode) { + if( envBlock != NULL ) { + DestroyEnvironmentBlock( envBlock ); + envBlock = NULL; + } + // We have to explictly Terminate, passing in the error code + // simply closing the job would kill our own process with success exit status + TerminateJobObject(jobObject, dwErrorCode); + + // This is tehnically dead code, we cannot reach this condition + return dwErrorCode; } CloseHandle(pi.hThread); @@ -251,15 +766,15 @@ DWORD CreateTaskImpl(__in_opt HANDLE logonHandle, __in PCWSTR jobObjName,__in PW WaitForSingleObject( pi.hProcess, INFINITE ); if(GetExitCodeProcess(pi.hProcess, &exitCode) == 0) { - dwErrorCode = GetLastError(); + dwErrorCode = GetLastError(); } CloseHandle( pi.hProcess ); - if( envBlock != NULL ) { - DestroyEnvironmentBlock( envBlock ); - envBlock = NULL; - } - + if( envBlock != NULL ) { + DestroyEnvironmentBlock( envBlock ); + envBlock = NULL; + } + // Terminate job object so that all spawned processes are also killed. // This is needed because once this process closes the handle to the job // object and none of the spawned objects have the handle open (via @@ -267,134 +782,133 @@ DWORD CreateTaskImpl(__in_opt HANDLE logonHandle, __in PCWSTR jobObjName,__in PW // program (say winutils task kill) to terminate this job object via its name. if(TerminateJobObject(jobObject, exitCode) == 0) { - dwErrorCode = GetLastError(); + dwErrorCode = GetLastError(); } - // comes here only on failure of TerminateJobObject + // comes here only on failure of TerminateJobObject CloseHandle(jobObject); - if(dwErrorCode != ERROR_SUCCESS) + if(dwErrorCode != ERROR_SUCCESS) { - return dwErrorCode; + return dwErrorCode; } return exitCode; } //---------------------------------------------------------------------------- -// Function: CreateTask -// -// Description: -// Creates a task via a jobobject. Outputs the -// appropriate information to stdout on success, or stderr on failure. -// -// Returns: -// ERROR_SUCCESS: On success -// GetLastError: otherwise -DWORD CreateTask(__in PCWSTR jobObjName,__in PWSTR cmdLine) -{ - // call with null logon in order to create tasks utilizing the current logon - return CreateTaskImpl( NULL, jobObjName, cmdLine ); -} -//---------------------------------------------------------------------------- -// Function: CreateTask -// -// Description: -// Creates a task via a jobobject. Outputs the -// appropriate information to stdout on success, or stderr on failure. -// -// Returns: -// ERROR_SUCCESS: On success -// GetLastError: otherwise -DWORD CreateTaskAsUser(__in PCWSTR jobObjName,__in PWSTR user, __in PWSTR pidFilePath, __in PWSTR cmdLine) -{ - DWORD err = ERROR_SUCCESS; - DWORD exitCode = EXIT_FAILURE; - ULONG authnPkgId; - HANDLE lsaHandle = INVALID_HANDLE_VALUE; - PROFILEINFO pi; - BOOL profileIsLoaded = FALSE; - FILE* pidFile = NULL; - - DWORD retLen = 0; - HANDLE logonHandle = NULL; - - err = EnablePrivilege(SE_TCB_NAME); - if( err != ERROR_SUCCESS ) { - fwprintf(stdout, L"INFO: The user does not have SE_TCB_NAME.\n"); - goto done; - } - err = EnablePrivilege(SE_ASSIGNPRIMARYTOKEN_NAME); - if( err != ERROR_SUCCESS ) { - fwprintf(stdout, L"INFO: The user does not have SE_ASSIGNPRIMARYTOKEN_NAME.\n"); - goto done; - } - err = EnablePrivilege(SE_INCREASE_QUOTA_NAME); - if( err != ERROR_SUCCESS ) { - fwprintf(stdout, L"INFO: The user does not have SE_INCREASE_QUOTA_NAME.\n"); - goto done; - } - err = EnablePrivilege(SE_RESTORE_NAME); - if( err != ERROR_SUCCESS ) { - fwprintf(stdout, L"INFO: The user does not have SE_RESTORE_NAME.\n"); - goto done; - } - - err = RegisterWithLsa(LOGON_PROCESS_NAME ,&lsaHandle); - if( err != ERROR_SUCCESS ) goto done; - - err = LookupKerberosAuthenticationPackageId( lsaHandle, &authnPkgId ); - if( err != ERROR_SUCCESS ) goto done; - - err = CreateLogonForUser(lsaHandle, - LOGON_PROCESS_NAME, - TOKEN_SOURCE_NAME, - authnPkgId, - user, - &logonHandle); - if( err != ERROR_SUCCESS ) goto done; - - err = LoadUserProfileForLogon(logonHandle, &pi); - if( err != ERROR_SUCCESS ) goto done; - profileIsLoaded = TRUE; - - // Create the PID file - - if (!(pidFile = _wfopen(pidFilePath, "w"))) { - err = GetLastError(); - goto done; - } - - if (0 > fprintf_s(pidFile, "%ls", jobObjName)) { - err = GetLastError(); - } - - fclose(pidFile); - - if (err != ERROR_SUCCESS) { - goto done; - } - - err = CreateTaskImpl(logonHandle, jobObjName, cmdLine); - -done: - if( profileIsLoaded ) { - UnloadProfileForLogon( logonHandle, &pi ); - profileIsLoaded = FALSE; - } - if( logonHandle != NULL ) { - CloseHandle(logonHandle); - } - - if (INVALID_HANDLE_VALUE != lsaHandle) { - UnregisterWithLsa(lsaHandle); - } - - return err; -} - - -//---------------------------------------------------------------------------- -// Function: IsTaskAlive +// Function: CreateTask +// +// Description: +// Creates a task via a jobobject. Outputs the +// appropriate information to stdout on success, or stderr on failure. +// +// Returns: +// ERROR_SUCCESS: On success +// GetLastError: otherwise +DWORD CreateTask(__in PCWSTR jobObjName,__in PWSTR cmdLine) +{ + // call with null logon in order to create tasks utilizing the current logon + return CreateTaskImpl( NULL, jobObjName, cmdLine, NULL); +} + +//---------------------------------------------------------------------------- +// Function: CreateTaskAsUser +// +// Description: +// Creates a task via a jobobject. Outputs the +// appropriate information to stdout on success, or stderr on failure. +// +// Returns: +// ERROR_SUCCESS: On success +// GetLastError: otherwise +DWORD CreateTaskAsUser(__in PCWSTR jobObjName, + __in PCWSTR user, __in PCWSTR pidFilePath, __in PCWSTR cmdLine) +{ + DWORD err = ERROR_SUCCESS; + DWORD exitCode = EXIT_FAILURE; + ULONG authnPkgId; + HANDLE lsaHandle = INVALID_HANDLE_VALUE; + PROFILEINFO pi; + BOOL profileIsLoaded = FALSE; + FILE* pidFile = NULL; + DWORD retLen = 0; + HANDLE logonHandle = NULL; + + err = EnableImpersonatePrivileges(); + if( err != ERROR_SUCCESS ) { + ReportErrorCode(L"EnableImpersonatePrivileges", err); + goto done; + } + + err = RegisterWithLsa(LOGON_PROCESS_NAME ,&lsaHandle); + if( err != ERROR_SUCCESS ) { + ReportErrorCode(L"RegisterWithLsa", err); + goto done; + } + + err = LookupKerberosAuthenticationPackageId( lsaHandle, &authnPkgId ); + if( err != ERROR_SUCCESS ) { + ReportErrorCode(L"LookupKerberosAuthenticationPackageId", err); + goto done; + } + + err = CreateLogonTokenForUser(lsaHandle, + LOGON_PROCESS_NAME, + TOKEN_SOURCE_NAME, + authnPkgId, + user, + &logonHandle); + if( err != ERROR_SUCCESS ) { + ReportErrorCode(L"CreateLogonTokenForUser", err); + goto done; + } + + err = LoadUserProfileForLogon(logonHandle, &pi); + if( err != ERROR_SUCCESS ) { + ReportErrorCode(L"LoadUserProfileForLogon", err); + goto done; + } + profileIsLoaded = TRUE; + + // Create the PID file + + if (!(pidFile = _wfopen(pidFilePath, "w"))) { + err = GetLastError(); + ReportErrorCode(L"_wfopen:pidFilePath", err); + goto done; + } + + if (0 > fprintf_s(pidFile, "%ls", jobObjName)) { + err = GetLastError(); + } + + fclose(pidFile); + + if (err != ERROR_SUCCESS) { + ReportErrorCode(L"fprintf_s:pidFilePath", err); + goto done; + } + + err = CreateTaskImpl(logonHandle, jobObjName, cmdLine, user); + +done: + if( profileIsLoaded ) { + UnloadProfileForLogon( logonHandle, &pi ); + profileIsLoaded = FALSE; + } + if( logonHandle != NULL ) { + CloseHandle(logonHandle); + } + + if (INVALID_HANDLE_VALUE != lsaHandle) { + UnregisterWithLsa(lsaHandle); + } + + return err; +} + +//---------------------------------------------------------------------------- +// Function: IsTaskAlive // // Description: // Checks if a task is alive via a jobobject. Outputs the @@ -403,15 +917,32 @@ done: // Returns: // ERROR_SUCCESS: On success // GetLastError: otherwise -DWORD IsTaskAlive(const WCHAR* jobObjName, int* isAlive, int* procsInJob) +DWORD IsTaskAlive(const WCHAR* jobObjName, int* isAlive, int* procsInJob) { PJOBOBJECT_BASIC_PROCESS_ID_LIST procList; HANDLE jobObject = NULL; int numProcs = 100; + WCHAR secureJobNameBuffer[MAX_PATH]; *isAlive = FALSE; jobObject = OpenJobObject(JOB_OBJECT_QUERY, FALSE, jobObjName); + if(jobObject == NULL) + { + // Try Global\... + DWORD err = GetSecureJobObjectName(jobObjName, MAX_PATH, secureJobNameBuffer); + if (err) { + ReportErrorCode(L"GetSecureJobObjectName", err); + return err; + } + jobObject = OpenJobObject(JOB_OBJECT_QUERY, FALSE, secureJobNameBuffer); + } + + if(jobObject == NULL) + { + DWORD err = GetLastError(); + return err; + } if(jobObject == NULL) { @@ -454,40 +985,7 @@ DWORD IsTaskAlive(const WCHAR* jobObjName, int* isAlive, int* procsInJob) } //---------------------------------------------------------------------------- -// Function: KillTask -// -// Description: -// Kills a task via a jobobject. Outputs the -// appropriate information to stdout on success, or stderr on failure. -// -// Returns: -// ERROR_SUCCESS: On success -// GetLastError: otherwise -DWORD KillTask(PCWSTR jobObjName) -{ - HANDLE jobObject = OpenJobObject(JOB_OBJECT_TERMINATE, FALSE, jobObjName); - if(jobObject == NULL) - { - DWORD err = GetLastError(); - if(err == ERROR_FILE_NOT_FOUND) - { - // job object does not exist. assume its not alive - return ERROR_SUCCESS; - } - return err; - } - - if(TerminateJobObject(jobObject, KILLED_PROCESS_EXIT_CODE) == 0) - { - return GetLastError(); - } - CloseHandle(jobObject); - - return ERROR_SUCCESS; -} - -//---------------------------------------------------------------------------- -// Function: PrintTaskProcessList +// Function: PrintTaskProcessList // // Description: // Prints resource usage of all processes in the task jobobject @@ -495,12 +993,26 @@ DWORD KillTask(PCWSTR jobObjName) // Returns: // ERROR_SUCCESS: On success // GetLastError: otherwise -DWORD PrintTaskProcessList(const WCHAR* jobObjName) +DWORD PrintTaskProcessList(const WCHAR* jobObjName) { DWORD i; PJOBOBJECT_BASIC_PROCESS_ID_LIST procList; int numProcs = 100; - HANDLE jobObject = OpenJobObject(JOB_OBJECT_QUERY, FALSE, jobObjName); + WCHAR secureJobNameBuffer[MAX_PATH]; + HANDLE jobObject = NULL; + + jobObject = OpenJobObject(JOB_OBJECT_QUERY, FALSE, jobObjName); + if(jobObject == NULL) + { + // Try Global\... + DWORD err = GetSecureJobObjectName(jobObjName, MAX_PATH, secureJobNameBuffer); + if (err) { + ReportErrorCode(L"GetSecureJobObjectName", err); + return err; + } + jobObject = OpenJobObject(JOB_OBJECT_QUERY, FALSE, secureJobNameBuffer); + } + if(jobObject == NULL) { DWORD err = GetLastError(); @@ -579,21 +1091,21 @@ int Task(__in int argc, __in_ecount(argc) wchar_t *argv[]) { DWORD dwErrorCode = ERROR_SUCCESS; TaskCommandOption command = TaskInvalid; - wchar_t* cmdLine = NULL; - wchar_t buffer[16*1024] = L""; // 32K max command line - size_t charCountBufferLeft = sizeof (buffer)/sizeof(wchar_t); - int crtArgIndex = 0; - size_t argLen = 0; - size_t wscatErr = 0; - wchar_t* insertHere = NULL; - - enum { - ARGC_JOBOBJECTNAME = 2, - ARGC_USERNAME, - ARGC_PIDFILE, - ARGC_COMMAND, - ARGC_COMMAND_ARGS - }; + wchar_t* cmdLine = NULL; + wchar_t buffer[16*1024] = L""; // 32K max command line + size_t charCountBufferLeft = sizeof(buffer)/sizeof(wchar_t); + int crtArgIndex = 0; + size_t argLen = 0; + size_t wscatErr = 0; + wchar_t* insertHere = NULL; + + enum { + ARGC_JOBOBJECTNAME = 2, + ARGC_USERNAME, + ARGC_PIDFILE, + ARGC_COMMAND, + ARGC_COMMAND_ARGS + }; if (!ParseCommandLine(argc, argv, &command)) { dwErrorCode = ERROR_INVALID_COMMAND_LINE; @@ -607,57 +1119,57 @@ int Task(__in int argc, __in_ecount(argc) wchar_t *argv[]) { // Create the task jobobject // - dwErrorCode = CreateTask(argv[2], argv[3]); - if (dwErrorCode != ERROR_SUCCESS) - { - ReportErrorCode(L"CreateTask", dwErrorCode); - goto TaskExit; - } - } else if (command == TaskCreateAsUser) - { - // Create the task jobobject as a domain user - // createAsUser accepts an open list of arguments. All arguments after the command are - // to be passed as argumrnts to the command itself.Here we're concatenating all - // arguments after the command into a single arg entry. - // - cmdLine = argv[ARGC_COMMAND]; - if (argc > ARGC_COMMAND_ARGS) { - crtArgIndex = ARGC_COMMAND; - insertHere = buffer; - while (crtArgIndex < argc) { - argLen = wcslen(argv[crtArgIndex]); - wscatErr = wcscat_s(insertHere, charCountBufferLeft, argv[crtArgIndex]); - switch (wscatErr) { - case 0: - // 0 means success; - break; - case EINVAL: - dwErrorCode = ERROR_INVALID_PARAMETER; - goto TaskExit; - case ERANGE: - dwErrorCode = ERROR_INSUFFICIENT_BUFFER; - goto TaskExit; - default: - // This case is not MSDN documented. - dwErrorCode = ERROR_GEN_FAILURE; - goto TaskExit; - } - insertHere += argLen; - charCountBufferLeft -= argLen; - insertHere[0] = L' '; - insertHere += 1; - charCountBufferLeft -= 1; - insertHere[0] = 0; - ++crtArgIndex; - } - cmdLine = buffer; - } - - dwErrorCode = CreateTaskAsUser( - argv[ARGC_JOBOBJECTNAME], argv[ARGC_USERNAME], argv[ARGC_PIDFILE], cmdLine); + dwErrorCode = CreateTask(argv[2], argv[3]); if (dwErrorCode != ERROR_SUCCESS) { - ReportErrorCode(L"CreateTaskAsUser", dwErrorCode); + ReportErrorCode(L"CreateTask", dwErrorCode); + goto TaskExit; + } + } else if (command == TaskCreateAsUser) + { + // Create the task jobobject as a domain user + // createAsUser accepts an open list of arguments. All arguments after the command are + // to be passed as argumrnts to the command itself.Here we're concatenating all + // arguments after the command into a single arg entry. + // + cmdLine = argv[ARGC_COMMAND]; + if (argc > ARGC_COMMAND_ARGS) { + crtArgIndex = ARGC_COMMAND; + insertHere = buffer; + while (crtArgIndex < argc) { + argLen = wcslen(argv[crtArgIndex]); + wscatErr = wcscat_s(insertHere, charCountBufferLeft, argv[crtArgIndex]); + switch (wscatErr) { + case 0: + // 0 means success; + break; + case EINVAL: + dwErrorCode = ERROR_INVALID_PARAMETER; + goto TaskExit; + case ERANGE: + dwErrorCode = ERROR_INSUFFICIENT_BUFFER; + goto TaskExit; + default: + // This case is not MSDN documented. + dwErrorCode = ERROR_GEN_FAILURE; + goto TaskExit; + } + insertHere += argLen; + charCountBufferLeft -= argLen; + insertHere[0] = L' '; + insertHere += 1; + charCountBufferLeft -= 1; + insertHere[0] = 0; + ++crtArgIndex; + } + cmdLine = buffer; + } + + dwErrorCode = CreateTaskAsUser( + argv[ARGC_JOBOBJECTNAME], argv[ARGC_USERNAME], argv[ARGC_PIDFILE], cmdLine); + if (dwErrorCode != ERROR_SUCCESS) + { + ReportErrorCode(L"CreateTaskAsUser", dwErrorCode); goto TaskExit; } } else if (command == TaskIsAlive) @@ -666,10 +1178,10 @@ int Task(__in int argc, __in_ecount(argc) wchar_t *argv[]) // int isAlive; int numProcs; - dwErrorCode = IsTaskAlive(argv[2], &isAlive, &numProcs); + dwErrorCode = IsTaskAlive(argv[2], &isAlive, &numProcs); if (dwErrorCode != ERROR_SUCCESS) { - ReportErrorCode(L"IsTaskAlive", dwErrorCode); + ReportErrorCode(L"IsTaskAlive", dwErrorCode); goto TaskExit; } @@ -681,27 +1193,27 @@ int Task(__in int argc, __in_ecount(argc) wchar_t *argv[]) else { dwErrorCode = ERROR_TASK_NOT_ALIVE; - ReportErrorCode(L"IsTaskAlive returned false", dwErrorCode); + ReportErrorCode(L"IsTaskAlive returned false", dwErrorCode); goto TaskExit; } } else if (command == TaskKill) { // Check if task jobobject // - dwErrorCode = KillTask(argv[2]); + dwErrorCode = KillTask(argv[2]); if (dwErrorCode != ERROR_SUCCESS) { - ReportErrorCode(L"KillTask", dwErrorCode); + ReportErrorCode(L"KillTask", dwErrorCode); goto TaskExit; } } else if (command == TaskProcessList) { // Check if task jobobject // - dwErrorCode = PrintTaskProcessList(argv[2]); + dwErrorCode = PrintTaskProcessList(argv[2]); if (dwErrorCode != ERROR_SUCCESS) { - ReportErrorCode(L"PrintTaskProcessList", dwErrorCode); + ReportErrorCode(L"PrintTaskProcessList", dwErrorCode); goto TaskExit; } } else @@ -712,6 +1224,7 @@ int Task(__in int argc, __in_ecount(argc) wchar_t *argv[]) } TaskExit: + ReportErrorCode(L"TaskExit:", dwErrorCode); return dwErrorCode; } @@ -722,12 +1235,12 @@ void TaskUsage() // ProcessTree.isSetsidSupported() fwprintf(stdout, L"\ Usage: task create [TASKNAME] [COMMAND_LINE] |\n\ - task createAsUser [TASKNAME] [USERNAME] [PIDFILE] [COMMAND_LINE] |\n\ + task createAsUser [TASKNAME] [USERNAME] [PIDFILE] [COMMAND_LINE] |\n\ task isAlive [TASKNAME] |\n\ task kill [TASKNAME]\n\ task processList [TASKNAME]\n\ Creates a new task jobobject with taskname\n\ - Creates a new task jobobject with taskname as the user provided\n\ + Creates a new task jobobject with taskname as the user provided\n\ Checks if task jobobject is alive\n\ Kills task jobobject\n\ Prints to stdout a list of processes in the task\n\ diff --git a/hadoop-common-project/hadoop-common/src/main/winutils/winutils.mc b/hadoop-common-project/hadoop-common/src/main/winutils/winutils.mc new file mode 100644 index 00000000000..2add06401e3 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/winutils/winutils.mc @@ -0,0 +1,64 @@ +;/* +; * Licensed to the Apache Software Foundation (ASF) under one +; * or more contributor license agreements. See the NOTICE file +; * distributed with this work for additional information +; * regarding copyright ownership. The ASF licenses this file +; * to you under the Apache License, Version 2.0 (the +; * "License"); you may not use this file except in compliance +; * with the License. You may obtain a copy of the License at +; * +; * http://www.apache.org/licenses/LICENSE-2.0 +; * +; * Unless required by applicable law or agreed to in writing, software +; * distributed under the License is distributed on an "AS IS" BASIS, +; * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +; * See the License for the specific language governing permissions and +; * limitations under the License. +; */ + +; // winutils.mc + +; // EventLog messages for Hadoop winutils service. + + +LanguageNames=(English=0x409:MSG00409) + + +; // The following are the categories of events. + +MessageIdTypedef=WORD + +MessageId=0x1 +SymbolicName=SERVICE_CATEGORY +Language=English +Service Events +. + +MessageId=0x2 +SymbolicName=LOG_CATEGORY +Language=English +Task Events +. + +; // The following are the message definitions. + +MessageIdTypedef=DWORD + +MessageId=0x80 +SymbolicName=MSG_CHECK_ERROR +Language=English +%1. Error %2: %3. +. + +MessageId=0x100 +SymbolicName=MSG_RPC_SERVICE_HAS_STARTED +Language=English +The LPC server is listenning. +. + +MessageId=0x200 +SymbolicName=MSG_RPC_SERVICE_HAS_STOPPED +Language=English +The LPC server has stopped listenning. +. + diff --git a/hadoop-common-project/hadoop-common/src/main/winutils/winutils.sln b/hadoop-common-project/hadoop-common/src/main/winutils/winutils.sln index d4e019e60d9..d2784b8f470 100644 --- a/hadoop-common-project/hadoop-common/src/main/winutils/winutils.sln +++ b/hadoop-common-project/hadoop-common/src/main/winutils/winutils.sln @@ -26,26 +26,16 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libwinutils", "libwinutils. EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 - Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D94B3BD7-39CC-47A0-AE9A-353FDE506F33}.Debug|Win32.ActiveCfg = Debug|x64 - {D94B3BD7-39CC-47A0-AE9A-353FDE506F33}.Debug|Win32.Build.0 = Debug|x64 - {D94B3BD7-39CC-47A0-AE9A-353FDE506F33}.Debug|x64.ActiveCfg = Debug|x64 - {D94B3BD7-39CC-47A0-AE9A-353FDE506F33}.Debug|x64.Build.0 = Debug|x64 - {D94B3BD7-39CC-47A0-AE9A-353FDE506F33}.Release|Win32.ActiveCfg = Release|Win32 - {D94B3BD7-39CC-47A0-AE9A-353FDE506F33}.Release|Win32.Build.0 = Release|Win32 + {D94B3BD7-39CC-47A0-AE9A-353FDE506F33}.Debug|x64.ActiveCfg = Release|x64 + {D94B3BD7-39CC-47A0-AE9A-353FDE506F33}.Debug|x64.Build.0 = Release|x64 {D94B3BD7-39CC-47A0-AE9A-353FDE506F33}.Release|x64.ActiveCfg = Release|x64 {D94B3BD7-39CC-47A0-AE9A-353FDE506F33}.Release|x64.Build.0 = Release|x64 - {12131AA7-902E-4A6D-9CE3-043261D22A12}.Debug|Win32.ActiveCfg = Debug|x64 - {12131AA7-902E-4A6D-9CE3-043261D22A12}.Debug|Win32.Build.0 = Debug|x64 - {12131AA7-902E-4A6D-9CE3-043261D22A12}.Debug|x64.ActiveCfg = Debug|x64 - {12131AA7-902E-4A6D-9CE3-043261D22A12}.Debug|x64.Build.0 = Debug|x64 - {12131AA7-902E-4A6D-9CE3-043261D22A12}.Release|Win32.ActiveCfg = Release|Win32 - {12131AA7-902E-4A6D-9CE3-043261D22A12}.Release|Win32.Build.0 = Release|Win32 + {12131AA7-902E-4A6D-9CE3-043261D22A12}.Debug|x64.ActiveCfg = Release|x64 + {12131AA7-902E-4A6D-9CE3-043261D22A12}.Debug|x64.Build.0 = Release|x64 {12131AA7-902E-4A6D-9CE3-043261D22A12}.Release|x64.ActiveCfg = Release|x64 {12131AA7-902E-4A6D-9CE3-043261D22A12}.Release|x64.Build.0 = Release|x64 EndGlobalSection diff --git a/hadoop-common-project/hadoop-common/src/main/winutils/winutils.vcxproj b/hadoop-common-project/hadoop-common/src/main/winutils/winutils.vcxproj index 5b9a195aea8..d7360849d04 100644 --- a/hadoop-common-project/hadoop-common/src/main/winutils/winutils.vcxproj +++ b/hadoop-common-project/hadoop-common/src/main/winutils/winutils.vcxproj @@ -19,18 +19,10 @@ - - Debug - Win32 - Debug x64 - - Release - Win32 - Release x64 @@ -42,22 +34,11 @@ winutils - - Application - true - Unicode - Application true Unicode - - Application - false - true - Unicode - Application false @@ -67,15 +48,9 @@ - - - - - - @@ -83,74 +58,32 @@ include;$(IncludePath) - - true - - - true - - ..\..\..\target\winutils\$(Configuration)\ - - - false - false - ..\..\..\target\winutils\$(Platform)\$(Configuration)\ - ..\..\..\target\bin\ - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - - Level4 Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + WIN32;_CONSOLE;_DEBUG;_UNICODE;UNICODE;WSCE_CONFIG_DIR=$(WsceConfigDir);WSCE_CONFIG_FILE=$(WsceConfigFile);%(PreprocessorDefinitions) Console true - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - - - Console - true - true - true - - Level3 - MaxSpeed + + Disabled true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + WIN32;_CONSOLE;NDEBUG;_UNICODE;UNICODE;WSCE_CONFIG_DIR=$(WsceConfigDir);WSCE_CONFIG_FILE=$(WsceConfigFile);%(PreprocessorDefinitions) Console @@ -159,7 +92,40 @@ true + + + $(IntermediateOutputPath) + + + Compiling Messages + mc.exe $(TargetName).mc -z $(TargetName)_msg -r $(IntermediateOutputPath) -h $(IntermediateOutputPath) -U + $(IntermediateOutputPath)$(TargetName)_msg.rc;$(IntermediateOutputPath)$(TargetName)_msg.h + + + true + X64 + $(IntermediateOutputPath) + true + true + true + 2 + + + + Midl + ClCompile,ResourceCompile + + + + + + + + + + + @@ -179,4 +145,4 @@ - \ No newline at end of file + diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/util/ProcessTree.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/util/ProcessTree.java index 2f8b84d5e1d..1e2d16ef41d 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/util/ProcessTree.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/util/ProcessTree.java @@ -296,7 +296,7 @@ public static boolean isAlive(String pid) { return false; } catch (IOException ioe) { LOG.warn("Error executing shell command " - + Arrays.toString(shexec.getExecString()) + ioe); + + shexec.toString() + ioe); return false; } return (shexec.getExitCode() == 0 ? true : false); @@ -321,7 +321,7 @@ public static boolean isProcessGroupAlive(String pgrpId) { return false; } catch (IOException ioe) { LOG.warn("Error executing shell command " - + Arrays.toString(shexec.getExecString()) + ioe); + + shexec.toString() + ioe); return false; } return (shexec.getExitCode() == 0 ? true : false); diff --git a/hadoop-yarn-project/CHANGES.txt b/hadoop-yarn-project/CHANGES.txt index 2b7a613146c..f323ff99b5d 100644 --- a/hadoop-yarn-project/CHANGES.txt +++ b/hadoop-yarn-project/CHANGES.txt @@ -151,6 +151,9 @@ Release 2.6.0 - UNRELEASED YARN-2504. Enhanced RM Admin CLI to support management of node-labels. (Wangda Tan via vinodkv) + YARN-2198. Remove the need to run NodeManager as privileged account for + Windows Secure Container Executor. (Remus Rusanu via jianhe) + IMPROVEMENTS YARN-2242. Improve exception information on AM launch crashes. (Li Lu diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/ContainerExecutor.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/ContainerExecutor.java index f5e63baa2de..3b866d3b1b9 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/ContainerExecutor.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/ContainerExecutor.java @@ -79,19 +79,23 @@ public Configuration getConf() { public abstract void init() throws IOException; /** - * On Windows the ContainerLaunch creates a temporary empty jar to workaround the CLASSPATH length - * In a secure cluster this jar must be localized so that the container has access to it + * On Windows the ContainerLaunch creates a temporary special jar manifest of + * other jars to workaround the CLASSPATH length. In a secure cluster this + * jar must be localized so that the container has access to it. * This function localizes on-demand the jar. * * @param classPathJar * @param owner * @throws IOException */ - public void localizeClasspathJar(Path classPathJar, String owner) throws IOException { - // For the default container this is a no-op - // The WindowsSecureContainerExecutor overrides this + public Path localizeClasspathJar(Path classPathJar, Path pwd, String owner) + throws IOException { + // Non-secure executor simply use the classpath created + // in the NM fprivate folder + return classPathJar; } - + + /** * Prepare the environment for containers in this application to execute. * For $x in local.dirs @@ -105,14 +109,13 @@ public void localizeClasspathJar(Path classPathJar, String owner) throws IOExcep * @param appId id of the application * @param nmPrivateContainerTokens path to localized credentials, rsrc by NM * @param nmAddr RPC address to contact NM - * @param localDirs nm-local-dirs - * @param logDirs nm-log-dirs + * @param dirsHandler NM local dirs service, for nm-local-dirs and nm-log-dirs * @throws IOException For most application init failures * @throws InterruptedException If application init thread is halted by NM */ public abstract void startLocalizer(Path nmPrivateContainerTokens, InetSocketAddress nmAddr, String user, String appId, String locId, - List localDirs, List logDirs) + LocalDirsHandlerService dirsHandler) throws IOException, InterruptedException; @@ -132,8 +135,8 @@ public abstract void startLocalizer(Path nmPrivateContainerTokens, */ public abstract int launchContainer(Container container, Path nmPrivateContainerScriptPath, Path nmPrivateTokensPath, - String user, String appId, Path containerWorkDir, List localDirs, - List logDirs) throws IOException; + String user, String appId, Path containerWorkDir, + List localDirs, List logDirs) throws IOException; public abstract boolean signalContainer(String user, String pid, Signal signal) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/DefaultContainerExecutor.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/DefaultContainerExecutor.java index 2e1d94d1bc7..834b138fcd2 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/DefaultContainerExecutor.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/DefaultContainerExecutor.java @@ -19,6 +19,7 @@ package org.apache.hadoop.yarn.server.nodemanager; import com.google.common.base.Optional; + import static org.apache.hadoop.fs.CreateFlag.CREATE; import static org.apache.hadoop.fs.CreateFlag.OVERWRITE; @@ -32,9 +33,11 @@ import java.util.EnumSet; import java.util.List; import java.util.Random; +import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileContext; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.UnsupportedFileSystemException; @@ -42,6 +45,7 @@ import org.apache.hadoop.io.IOUtils; import org.apache.hadoop.util.Shell; import org.apache.hadoop.util.Shell.ExitCodeException; +import org.apache.hadoop.util.Shell.CommandExecutor; import org.apache.hadoop.util.Shell.ShellCommandExecutor; import org.apache.hadoop.util.StringUtils; import org.apache.hadoop.yarn.api.records.ContainerId; @@ -92,13 +96,16 @@ public void init() throws IOException { @Override public synchronized void startLocalizer(Path nmPrivateContainerTokensPath, InetSocketAddress nmAddr, String user, String appId, String locId, - List localDirs, List logDirs) + LocalDirsHandlerService dirsHandler) throws IOException, InterruptedException { + List localDirs = dirsHandler.getLocalDirs(); + List logDirs = dirsHandler.getLogDirs(); + ContainerLocalizer localizer = new ContainerLocalizer(lfs, user, appId, locId, getPaths(localDirs), RecordFactoryProvider.getRecordFactory(getConf())); - + createUserLocalDirs(localDirs, user); createUserCacheDirs(localDirs, user); createAppDirs(localDirs, user, appId); @@ -120,9 +127,9 @@ public synchronized void startLocalizer(Path nmPrivateContainerTokensPath, @Override public int launchContainer(Container container, Path nmPrivateContainerScriptPath, Path nmPrivateTokensPath, - String userName, String appId, Path containerWorkDir, + String user, String appId, Path containerWorkDir, List localDirs, List logDirs) throws IOException { - + FsPermission dirPerm = new FsPermission(APPDIR_PERM); ContainerId containerId = container.getContainerId(); @@ -134,29 +141,30 @@ public int launchContainer(Container container, getApplicationId()); for (String sLocalDir : localDirs) { Path usersdir = new Path(sLocalDir, ContainerLocalizer.USERCACHE); - Path userdir = new Path(usersdir, userName); + Path userdir = new Path(usersdir, user); Path appCacheDir = new Path(userdir, ContainerLocalizer.APPCACHE); Path appDir = new Path(appCacheDir, appIdStr); Path containerDir = new Path(appDir, containerIdStr); - createDir(containerDir, dirPerm, true, userName); + createDir(containerDir, dirPerm, true, user); } // Create the container log-dirs on all disks - createContainerLogDirs(appIdStr, containerIdStr, logDirs, userName); + createContainerLogDirs(appIdStr, containerIdStr, logDirs, user); Path tmpDir = new Path(containerWorkDir, YarnConfiguration.DEFAULT_CONTAINER_TEMP_DIR); - createDir(tmpDir, dirPerm, false, userName); + createDir(tmpDir, dirPerm, false, user); - // copy launch script to work dir - Path launchDst = - new Path(containerWorkDir, ContainerLaunch.CONTAINER_SCRIPT); - copyFile(nmPrivateContainerScriptPath, launchDst, userName); // copy container tokens to work dir Path tokenDst = new Path(containerWorkDir, ContainerLaunch.FINAL_CONTAINER_TOKENS_FILE); - copyFile(nmPrivateTokensPath, tokenDst, userName); + copyFile(nmPrivateTokensPath, tokenDst, user); + + // copy launch script to work dir + Path launchDst = + new Path(containerWorkDir, ContainerLaunch.CONTAINER_SCRIPT); + copyFile(nmPrivateContainerScriptPath, launchDst, user); // Create new local launch wrapper script LocalWrapperScriptBuilder sb = getLocalWrapperScriptBuilder( @@ -181,23 +189,19 @@ public int launchContainer(Container container, + " was marked as inactive. Returning terminated error"); return ExitCode.TERMINATED.getExitCode(); } - + // create log dir under app // fork script - ShellCommandExecutor shExec = null; + Shell.CommandExecutor shExec = null; try { - setScriptExecutable(launchDst, userName); - setScriptExecutable(sb.getWrapperScriptPath(), userName); + setScriptExecutable(launchDst, user); + setScriptExecutable(sb.getWrapperScriptPath(), user); - // Setup command to run - String[] command = getRunCommand(sb.getWrapperScriptPath().toString(), - containerIdStr, userName, pidFile, this.getConf()); - - LOG.info("launchContainer: " + Arrays.toString(command)); - shExec = new ShellCommandExecutor( - command, + shExec = buildCommandExecutor(sb.getWrapperScriptPath().toString(), + containerIdStr, user, pidFile, new File(containerWorkDir.toUri().getPath()), - container.getLaunchContext().getEnvironment()); // sanitized env + container.getLaunchContext().getEnvironment()); + if (isContainerActive(containerId)) { shExec.execute(); } @@ -242,11 +246,26 @@ public int launchContainer(Container container, } return exitCode; } finally { - ; // + if (shExec != null) shExec.close(); } return 0; } + protected CommandExecutor buildCommandExecutor(String wrapperScriptPath, + String containerIdStr, String user, Path pidFile, File wordDir, + Map environment) + throws IOException { + + String[] command = getRunCommand(wrapperScriptPath, + containerIdStr, user, pidFile, this.getConf()); + + LOG.info("launchContainer: " + Arrays.toString(command)); + return new ShellCommandExecutor( + command, + wordDir, + environment); + } + protected LocalWrapperScriptBuilder getLocalWrapperScriptBuilder( String containerIdStr, Path containerWorkDir) { return Shell.WINDOWS ? @@ -421,7 +440,7 @@ public static boolean containerIsAlive(String pid) throws IOException { * @param signal signal to send * (for logging). */ - private void killContainer(String pid, Signal signal) throws IOException { + protected void killContainer(String pid, Signal signal) throws IOException { new ShellCommandExecutor(Shell.getSignalKillCommand(signal.getValue(), pid)) .execute(); } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/LinuxContainerExecutor.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/LinuxContainerExecutor.java index d628b1cde5b..884a16a1efa 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/LinuxContainerExecutor.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/LinuxContainerExecutor.java @@ -194,9 +194,12 @@ public void init() throws IOException { @Override public void startLocalizer(Path nmPrivateContainerTokensPath, InetSocketAddress nmAddr, String user, String appId, String locId, - List localDirs, List logDirs) + LocalDirsHandlerService dirsHandler) throws IOException, InterruptedException { + List localDirs = dirsHandler.getLocalDirs(); + List logDirs = dirsHandler.getLogDirs(); + verifyUsernamePattern(user); String runAsUser = getRunAsUser(user); List command = new ArrayList(); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/WindowsSecureContainerExecutor.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/WindowsSecureContainerExecutor.java index 578a9a20421..ec9c65e0111 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/WindowsSecureContainerExecutor.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/WindowsSecureContainerExecutor.java @@ -17,36 +17,272 @@ */ package org.apache.hadoop.yarn.server.nodemanager; +import java.io.BufferedReader; import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; import java.io.PrintStream; import java.net.InetSocketAddress; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.DelegateToFileSystem; +import org.apache.hadoop.fs.FileContext; import org.apache.hadoop.fs.FileUtil; +import org.apache.hadoop.fs.FsConstants; import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.RawLocalFileSystem; import org.apache.hadoop.fs.permission.FsPermission; +import org.apache.hadoop.io.nativeio.NativeIO.Windows; +import org.apache.hadoop.io.nativeio.NativeIOException; +import org.apache.hadoop.util.NativeCodeLoader; import org.apache.hadoop.util.Shell; -import org.apache.hadoop.util.Shell.ShellCommandExecutor; +import org.apache.hadoop.util.Shell.CommandExecutor; import org.apache.hadoop.yarn.conf.YarnConfiguration; -import org.apache.hadoop.yarn.server.nodemanager.DefaultContainerExecutor.LocalWrapperScriptBuilder; import org.apache.hadoop.yarn.server.nodemanager.containermanager.localizer.ContainerLocalizer; +import org.apache.hadoop.yarn.server.nodemanager.containermanager.localizer.ResourceLocalizationService; /** - * Windows secure container executor. Uses winutils task createAsUser. - * + * Windows secure container executor (WSCE). + * This class offers a secure container executor on Windows, similar to the + * LinuxContainerExecutor. As the NM does not run on a high privileged context, + * this class delegates elevated operations to the helper hadoopwintuilsvc, + * implemented by the winutils.exe running as a service. + * JNI and LRPC is used to communicate with the privileged service. */ public class WindowsSecureContainerExecutor extends DefaultContainerExecutor { - + private static final Log LOG = LogFactory .getLog(WindowsSecureContainerExecutor.class); + + public static final String LOCALIZER_PID_FORMAT = "STAR_LOCALIZER_%s"; + + + /** + * This class is a container for the JNI Win32 native methods used by WSCE. + */ + private static class Native { + private static boolean nativeLoaded = false; + + static { + if (NativeCodeLoader.isNativeCodeLoaded()) { + try { + initWsceNative(); + nativeLoaded = true; + } catch (Throwable t) { + LOG.info("Unable to initialize WSCE Native libraries", t); + } + } + } + + /** Initialize the JNI method ID and class ID cache */ + private static native void initWsceNative(); + + + /** + * This class contains methods used by the WindowsSecureContainerExecutor + * file system operations. + */ + public static class Elevated { + private static final int MOVE_FILE = 1; + private static final int COPY_FILE = 2; + + public static void mkdir(Path dirName) throws IOException { + if (!nativeLoaded) { + throw new IOException("Native WSCE libraries are required for mkdir"); + } + elevatedMkDirImpl(dirName.toString()); + } + + private static native void elevatedMkDirImpl(String dirName) + throws IOException; + + public static void chown(Path fileName, String user, String group) + throws IOException { + if (!nativeLoaded) { + throw new IOException("Native WSCE libraries are required for chown"); + } + elevatedChownImpl(fileName.toString(), user, group); + } + + private static native void elevatedChownImpl(String fileName, String user, + String group) throws IOException; + + public static void move(Path src, Path dst, boolean replaceExisting) + throws IOException { + if (!nativeLoaded) { + throw new IOException("Native WSCE libraries are required for move"); + } + elevatedCopyImpl(MOVE_FILE, src.toString(), dst.toString(), + replaceExisting); + } + + public static void copy(Path src, Path dst, boolean replaceExisting) + throws IOException { + if (!nativeLoaded) { + throw new IOException("Native WSCE libraries are required for copy"); + } + elevatedCopyImpl(COPY_FILE, src.toString(), dst.toString(), + replaceExisting); + } + + private static native void elevatedCopyImpl(int operation, String src, + String dst, boolean replaceExisting) throws IOException; + + public static void chmod(Path fileName, int mode) throws IOException { + if (!nativeLoaded) { + throw new IOException("Native WSCE libraries are required for chmod"); + } + elevatedChmodImpl(fileName.toString(), mode); + } + + private static native void elevatedChmodImpl(String path, int mode) + throws IOException; + + public static void killTask(String containerName) throws IOException { + if (!nativeLoaded) { + throw new IOException("Native WSCE libraries are required for killTask"); + } + elevatedKillTaskImpl(containerName); + } + + private static native void elevatedKillTaskImpl(String containerName) + throws IOException; + + public static OutputStream create(Path f, boolean append) + throws IOException { + if (!nativeLoaded) { + throw new IOException("Native WSCE libraries are required for create"); + } + + long desiredAccess = Windows.GENERIC_WRITE; + long shareMode = 0L; + long creationDisposition = append ? + Windows.OPEN_ALWAYS : Windows.CREATE_ALWAYS; + long flags = Windows.FILE_ATTRIBUTE_NORMAL; + + String fileName = f.toString(); + fileName = fileName.replace('/', '\\'); + + long hFile = elevatedCreateImpl( + fileName, desiredAccess, shareMode, creationDisposition, flags); + return new FileOutputStream( + WinutilsProcessStub.getFileDescriptorFromHandle(hFile)); + } + + private static native long elevatedCreateImpl(String path, + long desiredAccess, long shareMode, + long creationDisposition, long flags) throws IOException; + + + public static boolean deleteFile(Path path) throws IOException { + if (!nativeLoaded) { + throw new IOException("Native WSCE libraries are required for deleteFile"); + } + + return elevatedDeletePathImpl(path.toString(), false); + } + + public static boolean deleteDirectory(Path path) throws IOException { + if (!nativeLoaded) { + throw new IOException("Native WSCE libraries are required for deleteDirectory"); + } + + return elevatedDeletePathImpl(path.toString(), true); + } + + public native static boolean elevatedDeletePathImpl(String path, + boolean isDir) throws IOException; + } + + /** + * Wraps a process started by the winutils service helper. + * + */ + public static class WinutilsProcessStub extends Process { + + private final long hProcess; + private final long hThread; + private boolean disposed = false; + + private final InputStream stdErr; + private final InputStream stdOut; + private final OutputStream stdIn; + + public WinutilsProcessStub(long hProcess, long hThread, long hStdIn, + long hStdOut, long hStdErr) { + this.hProcess = hProcess; + this.hThread = hThread; + + this.stdIn = new FileOutputStream(getFileDescriptorFromHandle(hStdIn)); + this.stdOut = new FileInputStream(getFileDescriptorFromHandle(hStdOut)); + this.stdErr = new FileInputStream(getFileDescriptorFromHandle(hStdErr)); + } + + public static native FileDescriptor getFileDescriptorFromHandle(long handle); + + @Override + public native void destroy(); + + @Override + public native int exitValue(); + + @Override + public InputStream getErrorStream() { + return stdErr; + } + @Override + public InputStream getInputStream() { + return stdOut; + } + @Override + public OutputStream getOutputStream() { + return stdIn; + } + @Override + public native int waitFor() throws InterruptedException; + + public synchronized native void dispose(); + + public native void resume() throws NativeIOException; + } + + public synchronized static WinutilsProcessStub createTaskAsUser( + String cwd, String jobName, String user, String pidFile, String cmdLine) + throws IOException { + if (!nativeLoaded) { + throw new IOException( + "Native WSCE libraries are required for createTaskAsUser"); + } + synchronized(Shell.WindowsProcessLaunchLock) { + return createTaskAsUser0(cwd, jobName, user, pidFile, cmdLine); + } + } + + private static native WinutilsProcessStub createTaskAsUser0( + String cwd, String jobName, String user, String pidFile, String cmdLine) + throws NativeIOException; + } + + /** + * A shell script wrapper builder for WSCE. + * Overwrites the default behavior to remove the creation of the PID file in + * the script wrapper. WSCE creates the pid file as part of launching the + * task in winutils. + */ private class WindowsSecureWrapperScriptBuilder extends LocalWrapperScriptBuilder { @@ -60,21 +296,278 @@ protected void writeLocalWrapperScript(Path launchDst, Path pidFile, PrintStream } } + /** + * This is a skeleton file system used to elevate certain operations. + * WSCE has to create container dirs under local/userchache/$user but + * this dir itself is owned by $user, with chmod 750. As ther NM has no + * write access, it must delegate the write operations to the privileged + * hadoopwintuilsvc. + */ + private static class ElevatedFileSystem extends DelegateToFileSystem { + + /** + * This overwrites certain RawLocalSystem operations to be performed by a + * privileged process. + * + */ + private static class ElevatedRawLocalFilesystem extends RawLocalFileSystem { + + @Override + protected boolean mkOneDir(File p2f) throws IOException { + Path path = new Path(p2f.getAbsolutePath()); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("EFS:mkOneDir: %s", path)); + } + boolean ret = false; + + // File.mkdir returns false, does not throw. Must mimic it. + try { + Native.Elevated.mkdir(path); + ret = true; + } + catch(Throwable e) { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("EFS:mkOneDir: %s", + org.apache.hadoop.util.StringUtils.stringifyException(e))); + } + } + return ret; + } + + @Override + public void setPermission(Path p, FsPermission permission) + throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("EFS:setPermission: %s %s", p, permission)); + } + Native.Elevated.chmod(p, permission.toShort()); + } + + @Override + public void setOwner(Path p, String username, String groupname) + throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("EFS:setOwner: %s %s %s", + p, username, groupname)); + } + Native.Elevated.chown(p, username, groupname); + } + + @Override + protected OutputStream createOutputStream(Path f, boolean append) + throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("EFS:create: %s %b", f, append)); + } + return Native.Elevated.create(f, append); + } + + @Override + public boolean delete(Path p, boolean recursive) throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("EFS:delete: %s %b", p, recursive)); + } + + // The super delete uses the FileUtil.fullyDelete, + // but we cannot rely on that because we need to use the elevated + // operations to remove the files + // + File f = pathToFile(p); + if (!f.exists()) { + //no path, return false "nothing to delete" + return false; + } + else if (f.isFile()) { + return Native.Elevated.deleteFile(p); + } + else if (f.isDirectory()) { + + // This is a best-effort attempt. There are race conditions in that + // child files can be created/deleted after we snapped the list. + // No need to protect against that case. + File[] files = FileUtil.listFiles(f); + int childCount = files.length; + + if (recursive) { + for(File child:files) { + if (delete(new Path(child.getPath()), recursive)) { + --childCount; + } + } + } + if (childCount == 0) { + return Native.Elevated.deleteDirectory(p); + } + else { + throw new IOException("Directory " + f.toString() + " is not empty"); + } + } + else { + // This can happen under race conditions if an external agent + // is messing with the file type between IFs + throw new IOException("Path " + f.toString() + + " exists, but is neither a file nor a directory"); + } + } + } + + protected ElevatedFileSystem() throws IOException, URISyntaxException { + super(FsConstants.LOCAL_FS_URI, + new ElevatedRawLocalFilesystem(), + new Configuration(), + FsConstants.LOCAL_FS_URI.getScheme(), + false); + } + } + + private static class WintuilsProcessStubExecutor + implements Shell.CommandExecutor { + private Native.WinutilsProcessStub processStub; + private StringBuilder output = new StringBuilder(); + private int exitCode; + + private enum State { + INIT, + RUNNING, + COMPLETE + }; + + private State state;; + + private final String cwd; + private final String jobName; + private final String userName; + private final String pidFile; + private final String cmdLine; + + public WintuilsProcessStubExecutor( + String cwd, + String jobName, + String userName, + String pidFile, + String cmdLine) { + this.cwd = cwd; + this.jobName = jobName; + this.userName = userName; + this.pidFile = pidFile; + this.cmdLine = cmdLine; + this.state = State.INIT; + } + + private void assertComplete() throws IOException { + if (state != State.COMPLETE) { + throw new IOException("Process is not complete"); + } + } + + public String getOutput () throws IOException { + assertComplete(); + return output.toString(); + } + + public int getExitCode() throws IOException { + assertComplete(); + return exitCode; + } + + public void validateResult() throws IOException { + assertComplete(); + if (0 != exitCode) { + LOG.warn(output.toString()); + throw new IOException("Processs exit code is:" + exitCode); + } + } + + private Thread startStreamReader(final InputStream stream) + throws IOException { + Thread streamReaderThread = new Thread() { + + @Override + public void run() { + try + { + BufferedReader lines = new BufferedReader( + new InputStreamReader(stream)); + char[] buf = new char[512]; + int nRead; + while ((nRead = lines.read(buf, 0, buf.length)) > 0) { + output.append(buf, 0, nRead); + } + } + catch(Throwable t) { + LOG.error("Error occured reading the process stdout", t); + } + } + }; + streamReaderThread.start(); + return streamReaderThread; + } + + public void execute() throws IOException { + if (state != State.INIT) { + throw new IOException("Process is already started"); + } + processStub = Native.createTaskAsUser(cwd, + jobName, userName, pidFile, cmdLine); + state = State.RUNNING; + + Thread stdOutReader = startStreamReader(processStub.getInputStream()); + Thread stdErrReader = startStreamReader(processStub.getErrorStream()); + + try { + processStub.resume(); + processStub.waitFor(); + stdOutReader.join(); + stdErrReader.join(); + } + catch(InterruptedException ie) { + throw new IOException(ie); + } + + exitCode = processStub.exitValue(); + state = State.COMPLETE; + } + + @Override + public void close() { + if (processStub != null) { + processStub.dispose(); + } + } + } + private String nodeManagerGroup; + + /** + * Permissions for user WSCE dirs. + */ + static final short DIR_PERM = (short)0750; + + public WindowsSecureContainerExecutor() + throws IOException, URISyntaxException { + super(FileContext.getFileContext(new ElevatedFileSystem(), + new Configuration())); + } @Override public void setConf(Configuration conf) { super.setConf(conf); - nodeManagerGroup = conf.get(YarnConfiguration.NM_WINDOWS_SECURE_CONTAINER_GROUP); + nodeManagerGroup = conf.get( + YarnConfiguration.NM_WINDOWS_SECURE_CONTAINER_GROUP); } - + @Override protected String[] getRunCommand(String command, String groupId, String userName, Path pidFile, Configuration conf) { - return new String[] { Shell.WINUTILS, "task", "createAsUser", groupId, userName, - pidFile.toString(), "cmd /c " + command }; + File f = new File(command); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("getRunCommand: %s exists:%b", + command, f.exists())); + } + return new String[] { Shell.WINUTILS, "task", "createAsUser", groupId, + userName, pidFile.toString(), "cmd /c " + command }; } - + @Override protected LocalWrapperScriptBuilder getLocalWrapperScriptBuilder( String containerIdStr, Path containerWorkDir) { @@ -83,90 +576,156 @@ protected LocalWrapperScriptBuilder getLocalWrapperScriptBuilder( @Override protected void copyFile(Path src, Path dst, String owner) throws IOException { - super.copyFile(src, dst, owner); - lfs.setOwner(dst, owner, nodeManagerGroup); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("copyFile: %s -> %s owner:%s", src.toString(), + dst.toString(), owner)); + } + Native.Elevated.copy(src, dst, true); + Native.Elevated.chown(dst, owner, nodeManagerGroup); } @Override protected void createDir(Path dirPath, FsPermission perms, boolean createParent, String owner) throws IOException { + + // WSCE requires dirs to be 750, not 710 as DCE. + // This is similar to how LCE creates dirs + // + perms = new FsPermission(DIR_PERM); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("createDir: %s perm:%s owner:%s", + dirPath.toString(), perms.toString(), owner)); + } + super.createDir(dirPath, perms, createParent, owner); lfs.setOwner(dirPath, owner, nodeManagerGroup); } @Override - protected void setScriptExecutable(Path script, String owner) throws IOException { - super.setScriptExecutable(script, null); - lfs.setOwner(script, owner, nodeManagerGroup); + protected void setScriptExecutable(Path script, String owner) + throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("setScriptExecutable: %s owner:%s", + script.toString(), owner)); + } + super.setScriptExecutable(script, owner); + Native.Elevated.chown(script, owner, nodeManagerGroup); } @Override - public void localizeClasspathJar(Path classpathJar, String owner) throws IOException { - lfs.setOwner(classpathJar, owner, nodeManagerGroup); + public Path localizeClasspathJar(Path classPathJar, Path pwd, String owner) + throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("localizeClasspathJar: %s %s o:%s", + classPathJar, pwd, owner)); + } + createDir(pwd, new FsPermission(DIR_PERM), true, owner); + String fileName = classPathJar.getName(); + Path dst = new Path(pwd, fileName); + Native.Elevated.move(classPathJar, dst, true); + Native.Elevated.chown(dst, owner, nodeManagerGroup); + return dst; } @Override public void startLocalizer(Path nmPrivateContainerTokens, InetSocketAddress nmAddr, String user, String appId, String locId, - List localDirs, List logDirs) throws IOException, + LocalDirsHandlerService dirsHandler) throws IOException, InterruptedException { - + + List localDirs = dirsHandler.getLocalDirs(); + List logDirs = dirsHandler.getLogDirs(); + + Path classpathJarPrivateDir = dirsHandler.getLocalPathForWrite( + ResourceLocalizationService.NM_PRIVATE_DIR); createUserLocalDirs(localDirs, user); createUserCacheDirs(localDirs, user); createAppDirs(localDirs, user, appId); createAppLogDirs(appId, logDirs, user); Path appStorageDir = getWorkingDir(localDirs, user, appId); - - String tokenFn = String.format(ContainerLocalizer.TOKEN_FILE_NAME_FMT, locId); + + String tokenFn = String.format( + ContainerLocalizer.TOKEN_FILE_NAME_FMT, locId); Path tokenDst = new Path(appStorageDir, tokenFn); - LOG.info("Copying from " + nmPrivateContainerTokens + " to " + tokenDst); copyFile(nmPrivateContainerTokens, tokenDst, user); - List command ; - String[] commandArray; - ShellCommandExecutor shExec; - File cwdApp = new File(appStorageDir.toString()); - LOG.info(String.format("cwdApp: %s", cwdApp)); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("cwdApp: %s", cwdApp)); + } + + List command ; command = new ArrayList(); - command.add(Shell.WINUTILS); - command.add("task"); - command.add("createAsUser"); - command.add("START_LOCALIZER_" + locId); - command.add(user); - command.add("nul:"); // PID file - //use same jvm as parent - File jvm = new File(new File(System.getProperty("java.home"), "bin"), "java.exe"); + File jvm = new File( + new File(System.getProperty("java.home"), "bin"), "java.exe"); command.add(jvm.toString()); + Path cwdPath = new Path(cwdApp.getPath()); // Build a temp classpath jar. See ContainerLaunch.sanitizeEnv(). // Passing CLASSPATH explicitly is *way* too long for command line. String classPath = System.getProperty("java.class.path"); Map env = new HashMap(System.getenv()); - String[] jarCp = FileUtil.createJarWithClassPath(classPath, appStorageDir, env); - String classPathJar = jarCp[0]; - localizeClasspathJar(new Path(classPathJar), user); - String replacementClassPath = classPathJar + jarCp[1]; + String jarCp[] = FileUtil.createJarWithClassPath(classPath, + classpathJarPrivateDir, cwdPath, env); + String classPathJar = localizeClasspathJar( + new Path(jarCp[0]), cwdPath, user).toString(); command.add("-classpath"); - command.add(replacementClassPath); - + command.add(classPathJar + jarCp[1]); + String javaLibPath = System.getProperty("java.library.path"); if (javaLibPath != null) { command.add("-Djava.library.path=" + javaLibPath); } - ContainerLocalizer.buildMainArgs(command, user, appId, locId, nmAddr, localDirs); - commandArray = command.toArray(new String[command.size()]); + ContainerLocalizer.buildMainArgs(command, user, appId, locId, nmAddr, + localDirs); + + String cmdLine = StringUtils.join(command, " "); + + String localizerPid = String.format(LOCALIZER_PID_FORMAT, locId); + + WintuilsProcessStubExecutor stubExecutor = new WintuilsProcessStubExecutor( + cwdApp.getAbsolutePath(), + localizerPid, user, "nul:", cmdLine); + try { + stubExecutor.execute(); + stubExecutor.validateResult(); + } + finally { + stubExecutor.close(); + try + { + killContainer(localizerPid, Signal.KILL); + } + catch(Throwable e) { + LOG.warn(String.format( + "An exception occured during the cleanup of localizer job %s:\n%s", + localizerPid, + org.apache.hadoop.util.StringUtils.stringifyException(e))); + } + } + } + + @Override + protected CommandExecutor buildCommandExecutor(String wrapperScriptPath, + String containerIdStr, + String userName, Path pidFile,File wordDir, Map environment) + throws IOException { - shExec = new ShellCommandExecutor( - commandArray, cwdApp); - - shExec.execute(); + return new WintuilsProcessStubExecutor( + wordDir.toString(), + containerIdStr, userName, pidFile.toString(), + "cmd /c " + wrapperScriptPath); + } + + @Override + protected void killContainer(String pid, Signal signal) throws IOException { + Native.Elevated.killTask(pid); } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/launcher/ContainerLaunch.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/launcher/ContainerLaunch.java index 30abef55281..434cb4e4721 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/launcher/ContainerLaunch.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/launcher/ContainerLaunch.java @@ -212,7 +212,9 @@ public Integer call() { + Path.SEPARATOR + String.format(ContainerLocalizer.TOKEN_FILE_NAME_FMT, containerIdStr)); - + Path nmPrivateClasspathJarDir = + dirsHandler.getLocalPathForWrite( + getContainerPrivateDir(appIdStr, containerIdStr)); DataOutputStream containerScriptOutStream = null; DataOutputStream tokensOutStream = null; @@ -263,7 +265,7 @@ public Integer call() { FINAL_CONTAINER_TOKENS_FILE).toUri().getPath()); // Sanitize the container's environment sanitizeEnv(environment, containerWorkDir, appDirs, containerLogDirs, - localResources); + localResources, nmPrivateClasspathJarDir); // Write out the environment writeLaunchEnv(containerScriptOutStream, environment, localResources, @@ -658,7 +660,8 @@ private static void putEnvIfAbsent( public void sanitizeEnv(Map environment, Path pwd, List appDirs, List containerLogDirs, - Map> resources) throws IOException { + Map> resources, + Path nmPrivateClasspathJarDir) throws IOException { /** * Non-modifiable environment variables */ @@ -722,6 +725,7 @@ public void sanitizeEnv(Map environment, Path pwd, // TODO: Remove Windows check and use this approach on all platforms after // additional testing. See YARN-358. if (Shell.WINDOWS) { + String inputClassPath = environment.get(Environment.CLASSPATH.name()); if (inputClassPath != null && !inputClassPath.isEmpty()) { StringBuilder newClassPath = new StringBuilder(inputClassPath); @@ -763,13 +767,13 @@ public void sanitizeEnv(Map environment, Path pwd, Map mergedEnv = new HashMap( System.getenv()); mergedEnv.putAll(environment); - + String[] jarCp = FileUtil.createJarWithClassPath( - newClassPath.toString(), pwd, mergedEnv); - String classPathJar = jarCp[0]; + newClassPath.toString(), nmPrivateClasspathJarDir, pwd, mergedEnv); // In a secure cluster the classpath jar must be localized to grant access - this.exec.localizeClasspathJar(new Path(classPathJar), container.getUser()); - String replacementClassPath = classPathJar + jarCp[1]; + Path localizedClassPathJar = exec.localizeClasspathJar( + new Path(jarCp[0]), pwd, container.getUser()); + String replacementClassPath = localizedClassPathJar.toString() + jarCp[1]; environment.put(Environment.CLASSPATH.name(), replacementClassPath); } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/ContainerLocalizer.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/ContainerLocalizer.java index 35251706d15..32e355318d2 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/ContainerLocalizer.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/ContainerLocalizer.java @@ -369,10 +369,15 @@ public static void main(String[] argv) throws Throwable { new ContainerLocalizer(FileContext.getLocalFSFileContext(), user, appId, locId, localDirs, RecordFactoryProvider.getRecordFactory(null)); - System.exit(localizer.runLocalization(nmAddr)); + int nRet = localizer.runLocalization(nmAddr); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("nRet: %d", nRet)); + } + System.exit(nRet); } catch (Throwable e) { // Print error to stdout so that LCE can use it. e.printStackTrace(System.out); + LOG.error("Exception in main:", e); throw e; } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/ResourceLocalizationService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/ResourceLocalizationService.java index 371684b71dc..a6143a2aae9 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/ResourceLocalizationService.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/ResourceLocalizationService.java @@ -1088,7 +1088,8 @@ public void run() { ConverterUtils.toString( context.getContainerId(). getApplicationAttemptId().getApplicationId()), - localizerId, localDirs, logDirs); + localizerId, + dirsHandler); } else { throw new IOException("All disks failed. " + dirsHandler.getDisksHealthReport(false)); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/TestDefaultContainerExecutor.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/TestDefaultContainerExecutor.java index 7db74f382cf..bf01fb7814a 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/TestDefaultContainerExecutor.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/TestDefaultContainerExecutor.java @@ -303,6 +303,7 @@ public Object answer(InvocationOnMock invocationOnMock) public void testStartLocalizer() throws IOException, InterruptedException { InetSocketAddress localizationServerAddress; + final Path firstDir = new Path(BASE_TMP_PATH, "localDir1"); List localDirs = new ArrayList(); final Path secondDir = new Path(BASE_TMP_PATH, "localDir2"); @@ -383,9 +384,14 @@ public Object answer(InvocationOnMock invocationOnMock) String appSubmitter = "nobody"; String appId = "APP_ID"; String locId = "LOC_ID"; + + LocalDirsHandlerService dirsHandler = mock(LocalDirsHandlerService.class); + when(dirsHandler.getLocalDirs()).thenReturn(localDirs); + when(dirsHandler.getLogDirs()).thenReturn(logDirs); + try { mockExec.startLocalizer(nmPrivateCTokensPath, localizationServerAddress, - appSubmitter, appId, locId, localDirs, logDirs); + appSubmitter, appId, locId, dirsHandler); } catch (IOException e) { Assert.fail("StartLocalizer failed to copy token file " + e); } finally { diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/TestLinuxContainerExecutor.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/TestLinuxContainerExecutor.java index 67175d9d807..ff477a38ec7 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/TestLinuxContainerExecutor.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/TestLinuxContainerExecutor.java @@ -266,7 +266,7 @@ public void buildMainArgs(List command, String user, String appId, exec.setConf(conf); exec.startLocalizer(nmPrivateContainerTokensPath, nmAddr, appSubmitter, - appId, locId, localDirs, logDirs); + appId, locId, dirsHandler); String locId2 = "container_01_02"; Path nmPrivateContainerTokensPath2 = @@ -276,7 +276,7 @@ public void buildMainArgs(List command, String user, String appId, + String.format(ContainerLocalizer.TOKEN_FILE_NAME_FMT, locId2)); files.create(nmPrivateContainerTokensPath2, EnumSet.of(CREATE, OVERWRITE)); exec.startLocalizer(nmPrivateContainerTokensPath2, nmAddr, appSubmitter, - appId, locId2, localDirs, logDirs); + appId, locId2, dirsHandler); } @Test diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/TestLinuxContainerExecutorWithMocks.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/TestLinuxContainerExecutorWithMocks.java index 8a649ba941f..643b7c26b73 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/TestLinuxContainerExecutorWithMocks.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/TestLinuxContainerExecutorWithMocks.java @@ -184,7 +184,7 @@ public void testStartLocalizer() throws IOException { Path nmPrivateCTokensPath= new Path("file:///bin/nmPrivateCTokensPath"); try { - mockExec.startLocalizer(nmPrivateCTokensPath, address, "test", "application_0", "12345", dirsHandler.getLocalDirs(), dirsHandler.getLogDirs()); + mockExec.startLocalizer(nmPrivateCTokensPath, address, "test", "application_0", "12345", dirsHandler); List result=readMockParams(); Assert.assertEquals(result.size(), 17); Assert.assertEquals(result.get(0), YarnConfiguration.DEFAULT_NM_NONSECURE_MODE_LOCAL_USER); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/TestResourceLocalizationService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/TestResourceLocalizationService.java index d569fa797c0..cf1e9fa714d 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/TestResourceLocalizationService.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/localizer/TestResourceLocalizationService.java @@ -850,7 +850,7 @@ public boolean matches(Object o) { ArgumentCaptor tokenPathCaptor = ArgumentCaptor.forClass(Path.class); verify(exec).startLocalizer(tokenPathCaptor.capture(), isA(InetSocketAddress.class), eq("user0"), eq(appStr), eq(ctnrStr), - isA(List.class), isA(List.class)); + isA(LocalDirsHandlerService.class)); Path localizationTokenPath = tokenPathCaptor.getValue(); // heartbeat from localizer diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/SecureContainer.apt.vm b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/SecureContainer.apt.vm index 1f9688affa8..722870ca38a 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/SecureContainer.apt.vm +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/SecureContainer.apt.vm @@ -83,11 +83,15 @@ min.user.id=1000#Prevent other super-users +---+ - ** Windows Secure Container Executor + ** Windows Secure Container Executor (WSCE) The Windows environment secure container executor is the <<>>. It uses the Windows S4U infrastructure to launch the container as the - YARN application user. + YARN application user. The WSCE requires the presense of the <<>> service. This services + is hosted by <<<%HADOOP_HOME%\bin\winutils.exe>>> started with the <<>> command line argument. This + service offers some privileged operations that require LocalSystem authority so that the NM is not required + to run the entire JVM and all the NM code in an elevated context. The NM interacts with the <<>> + service by means of Local RPC (LRPC) via calls JNI to the RCP client hosted in <<>>. *** Configuration @@ -102,17 +106,71 @@ min.user.id=1000#Prevent other super-users yarn.nodemanager.windows-secure-container-executor.group - hadoop + yarn +---+ + *** wsce-site.xml + + The hadoopwinutilsvc uses <<<%HADOOP_HOME%\etc\hadoop\wsce_site.xml to configure access to the privileged operations. - The NodeManager must run as a member of the local <<>> group or as - <<>>. It is not enough for the NodeManager to simply impersonate such an user. ++---+ + + yarn.nodemanager.windows-secure-container-executor.impersonate.allowed + HadoopUsers + + + + yarn.nodemanager.windows-secure-container-executor.impersonate.denied + HadoopServices,Administrators + + + + yarn.nodemanager.windows-secure-container-executor.allowed + nodemanager + + + + yarn.nodemanager.windows-secure-container-executor.local-dirs + nm-local-dir, nm-log-dirs + + + + yarn.nodemanager.windows-secure-container-executor.job-name + nodemanager-job-name + ++---+ + + <<>> should contain the name of the service account running the + nodemanager. This user will be allowed to access the hadoopwintuilsvc functions. + + <<>> should contain the users that are allowed to create + containers in the cluster. These users will be allowed to be impersonated by hadoopwinutilsvc. + + <<>> should contain users that are explictly forbiden from + creating containers. hadoopwinutilsvc will refuse to impersonate these users. + + <<>> should contain the nodemanager local dirs. hadoopwinutilsvc will + allow only file operations under these directories. This should contain the same values as <<<${yarn.nodemanager.local-dirs}, ${yarn.nodemanager.log-dirs}>>> + but note that hadoopwinutilsvc XML configuration processing does not do substitutions so the value must be the final value. All paths + must be absolute and no environment variable substitution will be performed. The paths are compared LOCAL_INVARIANT case insensitive string comparison, + the file path validated must start with one of the paths listed in local-dirs configuration. Use comma as path separator:<<<,>>> + + <<>> should contain an Windows NT job name that all containers should be added to. + This configuration is optional. If not set, the container is not added to a global NodeManager job. Normally this should be set to the job that the NM is assigned to, + so that killing the NM kills also all containers. Hadoopwinutilsvc will not attempt to create this job, the job must exists when the container is launched. + If the value is set and the job does not exists, container launch will fail with error 2 <<>>. + Note that this global NM job is not related to the container job, which always gets created for each container and is named after the container ID. + This setting controls a global job that spans all containers and the parent NM, and as such it requires nested jobs. + Nested jobs are available only post Windows 8 and Windows Server 2012. *** Useful Links * {{{http://msdn.microsoft.com/en-us/magazine/cc188757.aspx}Exploring S4U Kerberos Extensions in Windows Server 2003}} + * {{{http://msdn.microsoft.com/en-us/library/windows/desktop/hh448388(v=vs.85).aspx}Nested Jobs}} + * {{{https://issues.apache.org/jira/browse/YARN-1063}Winutils needs ability to create task as domain user}} * {{{https://issues.apache.org/jira/browse/YARN-1972}Implement secure Windows Container Executor}} + + * {{{https://issues.apache.org/jira/browse/YARN-2198}Remove the need to run NodeManager as privileged account for Windows Secure Container Executor}} \ No newline at end of file