/* * Samba Unix/Linux CIFS implementation * * winexe * * Copyright (C) 2018 Volker Lendecke * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "includes.h" #include "version.h" #include #include #include "lib/param/param.h" #include "auth/credentials/credentials.h" #include "lib/util/talloc_stack.h" #include "lib/util/tevent_ntstatus.h" #include "lib/util/sys_rw.h" #include "libsmb/proto.h" #include "librpc/gen_ndr/ndr_svcctl_c.h" #include "rpc_client/cli_pipe.h" #include "libcli/smb/smbXcli_base.h" #include "libcli/util/werror.h" #include "lib/async_req/async_sock.h" #include "lib/cmdline/cmdline.h" #include "client.h" #define SVC_INTERACTIVE 1 #define SVC_IGNORE_INTERACTIVE 2 #define SVC_INTERACTIVE_MASK 3 #define SVC_FORCE_UPLOAD 4 #define SVC_OS64BIT 8 #define SVC_OSCHOOSE 16 #define SVC_UNINSTALL 32 #define SVC_SYSTEM 64 #define SERVICE_NAME "winexesvc" #define PIPE_NAME "ahexec" #define PIPE_NAME_IN "ahexec_stdin%08X" #define PIPE_NAME_OUT "ahexec_stdout%08X" #define PIPE_NAME_ERR "ahexec_stderr%08X" static const char version_message_fmt[] = "winexe version %d.%d\n" "This program may be freely redistributed under the terms of the " "GNU GPLv3\n"; struct program_options { char *hostname; int port; char *cmd; struct cli_credentials *credentials; char *runas; char *runas_file; int flags; }; static void parse_args(int argc, const char *argv[], TALLOC_CTX *mem_ctx, struct program_options *options) { poptContext pc; int opt, i; int argc_new; char **argv_new; int port = 445; char *port_str = NULL; int flag_interactive = SVC_IGNORE_INTERACTIVE; int flag_ostype = 2; int flag_reinstall = 0; int flag_uninstall = 0; int flag_help = 0; int flag_version = 0; bool ok; struct poptOption long_options[] = { POPT_AUTOHELP { .longName = "uninstall", .shortName = 0, .argInfo = POPT_ARG_NONE, .arg = &flag_uninstall, .val = 0, .descrip = "Uninstall winexe service after " "remote execution", .argDescrip = NULL, },{ .longName = "reinstall", .shortName = 0, .argInfo = POPT_ARG_NONE, .arg = &flag_reinstall, .val = 0, .descrip = "Reinstall winexe service before " "remote execution", .argDescrip = NULL, },{ .longName = "runas", .shortName = 0, .argInfo = POPT_ARG_STRING, .arg = &options->runas, .val = 0, .descrip = "Run as the given user (BEWARE: this " "password is sent in cleartext over " "the network!)", .argDescrip = "[DOMAIN\\]USERNAME%PASSWORD", },{ .longName = "runas-file", .shortName = 0, .argInfo = POPT_ARG_STRING, .arg = &options->runas_file, .val = 0, .descrip = "Run as user options defined in a file", .argDescrip = "FILE", },{ .longName = "interactive", .shortName = 0, .argInfo = POPT_ARG_INT, .arg = &flag_interactive, .val = 0, .descrip = "Desktop interaction: 0 - disallow, " "1 - allow. If allow, also use the " "--system switch (Windows requirement). " "Vista does not support this option.", .argDescrip = "0|1", },{ .longName = "ostype", .shortName = 0, .argInfo = POPT_ARG_INT, .arg = &flag_ostype, .val = 0, .descrip = "OS type: 0 - 32-bit, 1 - 64-bit, " "2 - winexe will decide. " "Determines which version (32-bit or 64-bit)" " of service will be installed.", .argDescrip = "0|1|2", }, POPT_COMMON_SAMBA POPT_COMMON_CREDENTIALS POPT_COMMON_VERSION POPT_TABLEEND }; ZERO_STRUCTP(options); ok = samba_cmdline_init(mem_ctx, SAMBA_CMDLINE_CONFIG_CLIENT, false /* require_smbconf */); if (!ok) { DBG_ERR("Failed to init cmdline parser!\n"); TALLOC_FREE(mem_ctx); exit(1); } pc = samba_popt_get_context(getprogname(), argc, argv, long_options, 0); if (pc == NULL) { DBG_ERR("Failed to setup popt context!\n"); TALLOC_FREE(mem_ctx); exit(1); } poptSetOtherOptionHelp(pc, "[OPTION]... //HOST[:PORT] COMMAND\nOptions:"); if (((opt = poptGetNextOpt(pc)) != -1) || flag_help || flag_version) { fprintf(stderr, version_message_fmt, SAMBA_VERSION_MAJOR, SAMBA_VERSION_MINOR); if (flag_version) { exit(0); } poptPrintHelp(pc, stdout, 0); if (flag_help) { exit(0); } exit(1); } argv_new = discard_const_p(char *, poptGetArgs(pc)); argc_new = argc; for (i = 0; i < argc; i++) { if (!argv_new || argv_new[i] == NULL) { argc_new = i; break; } } if (argc_new != 2 || argv_new[0][0] != '/' || argv_new[0][1] != '/') { fprintf(stderr, version_message_fmt, SAMBA_VERSION_MAJOR, SAMBA_VERSION_MINOR); poptPrintHelp(pc, stdout, 0); exit(1); } port_str = strchr(argv_new[0], ':'); if (port_str) { if (sscanf(port_str + 1, "%d", &port) != 1 || port <= 0) { fprintf(stderr, version_message_fmt, SAMBA_VERSION_MAJOR, SAMBA_VERSION_MINOR); poptPrintHelp(pc, stdout, 0); exit(1); } *port_str = '\0'; } if (options->runas == NULL && options->runas_file != NULL) { struct cli_credentials *runas_cred; const char *user; const char *pass; runas_cred = cli_credentials_init(mem_ctx); cli_credentials_parse_file(runas_cred, options->runas_file, CRED_SPECIFIED); user = cli_credentials_get_username(runas_cred); pass = cli_credentials_get_password(runas_cred); if (user && pass) { char buffer[1024]; const char *dom; dom = cli_credentials_get_domain(runas_cred); if (dom) { snprintf(buffer, sizeof(buffer), "%s\\%s%%%s", dom, user, pass); } else { snprintf(buffer, sizeof(buffer), "%s%%%s", user, pass); } buffer[sizeof(buffer)-1] = '\0'; options->runas = talloc_strdup(mem_ctx, buffer); } } options->credentials = samba_cmdline_get_creds(); options->hostname = talloc_strdup(mem_ctx, argv_new[0] + 2); if (options->hostname == NULL) { DBG_ERR("Out of memory\n"); exit(1); } options->port = port; options->cmd = talloc_strdup(mem_ctx, argv_new[1]); if (options->cmd == NULL) { DBG_ERR("Out of memory\n"); exit(1); } poptFreeContext(pc); options->flags = flag_interactive; if (flag_reinstall) { options->flags |= SVC_FORCE_UPLOAD; } if (flag_ostype == 1) { options->flags |= SVC_OS64BIT; } if (flag_ostype == 2) { options->flags |= SVC_OSCHOOSE; } if (flag_uninstall) { options->flags |= SVC_UNINSTALL; } } static NTSTATUS winexe_svc_upload( const char *hostname, int port, const char *service_filename, const DATA_BLOB *svc32_exe, const DATA_BLOB *svc64_exe, struct cli_credentials *credentials, int flags) { struct cli_state *cli; uint16_t fnum = 0xffff; NTSTATUS status; const DATA_BLOB *binary = NULL; status = cli_full_connection_creds( &cli, NULL, hostname, NULL, port, "ADMIN$", "?????", credentials, 0); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("cli_full_connection_creds failed: %s\n", nt_errstr(status)); return status; } if (flags & SVC_FORCE_UPLOAD) { status = cli_unlink(cli, service_filename, 0); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("cli_unlink failed: %s\n", nt_errstr(status)); } } if (flags & SVC_OSCHOOSE) { status = cli_chkpath(cli, "SysWoW64"); if (NT_STATUS_IS_OK(status)) { flags |= SVC_OS64BIT; } } if (flags & SVC_OS64BIT) { binary = svc64_exe; } else { binary = svc32_exe; } if (binary == NULL) { goto done; } status = cli_ntcreate( cli, service_filename, 0, /* CreatFlags */ SEC_FILE_WRITE_DATA, /* DesiredAccess */ FILE_ATTRIBUTE_NORMAL, /* FileAttributes */ FILE_SHARE_WRITE|FILE_SHARE_READ, /* ShareAccess */ FILE_OPEN_IF, /* CreateDisposition */ FILE_NON_DIRECTORY_FILE, /* CreateOptions */ 0, /* SecurityFlags */ &fnum, NULL); /* CreateReturns */ if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("Could not create %s: %s\n", service_filename, nt_errstr(status)); goto done; } status = cli_writeall( cli, fnum, 0, binary->data, 0, binary->length, NULL); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("Could not write file: %s\n", nt_errstr(status)); goto done; } done: if (fnum != 0xffff) { status = cli_close(cli, fnum); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("Close(%"PRIu16") failed for %s: %s\n", fnum, service_filename, nt_errstr(status)); } } TALLOC_FREE(cli); return status; } static NTSTATUS winexe_svc_install( struct cli_state *cli, const char *hostname, int port, const char *service_name, const char *service_filename, const DATA_BLOB *svc32_exe, const DATA_BLOB *svc64_exe, struct cli_credentials *credentials, int flags) { TALLOC_CTX *frame = talloc_stackframe(); struct rpc_pipe_client *rpccli; struct policy_handle scmanager_handle; struct policy_handle service_handle; struct SERVICE_STATUS service_status; bool need_start = false; bool need_conf = false; NTSTATUS status; WERROR werr; const char *remote_name = smbXcli_conn_remote_name(cli->conn); const struct sockaddr_storage *remote_sockaddr = smbXcli_conn_remote_sockaddr(cli->conn); status = cli_rpc_pipe_open_noauth_transport( cli, NCACN_NP, &ndr_table_svcctl, remote_name, remote_sockaddr, &rpccli); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("cli_rpc_pipe_open_noauth_transport failed: %s\n", nt_errstr(status)); goto done; } status = dcerpc_svcctl_OpenSCManagerW( rpccli->binding_handle, frame, remote_name, NULL, SEC_FLAG_MAXIMUM_ALLOWED, &scmanager_handle, &werr); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("dcerpc_svcctl_OpenSCManagerW failed: %s\n", nt_errstr(status)); goto done; } if (!W_ERROR_IS_OK(werr)) { DBG_WARNING("dcerpc_svcctl_OpenSCManagerW failed: %s\n", win_errstr(werr)); goto done; } status = dcerpc_svcctl_OpenServiceW( rpccli->binding_handle, frame, &scmanager_handle, service_name, SERVICE_ALL_ACCESS, &service_handle, &werr); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("dcerpc_svcctl_OpenServiceW failed: %s\n", nt_errstr(status)); goto close_scmanager; } if (W_ERROR_EQUAL(werr, WERR_SERVICE_DOES_NOT_EXIST)) { status = dcerpc_svcctl_CreateServiceW( rpccli->binding_handle, frame, &scmanager_handle, service_name, NULL, SERVICE_ALL_ACCESS, SERVICE_TYPE_WIN32_OWN_PROCESS | ((flags & SVC_INTERACTIVE) ? SERVICE_TYPE_INTERACTIVE_PROCESS : 0), SVCCTL_DEMAND_START, SVCCTL_SVC_ERROR_NORMAL, service_filename, NULL, NULL, NULL, 0, NULL, NULL, 0, &service_handle, &werr); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("dcerpc_svcctl_CreateServiceW " "failed: %s\n", nt_errstr(status)); goto close_scmanager; } if (!W_ERROR_IS_OK(werr)) { DBG_WARNING("dcerpc_svcctl_CreateServiceW " "failed: %s\n", win_errstr(werr)); status = werror_to_ntstatus(werr); goto close_scmanager; } } status = dcerpc_svcctl_QueryServiceStatus( rpccli->binding_handle, frame, &service_handle, &service_status, &werr); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("dcerpc_svcctl_QueryServiceStatus " "failed: %s\n", nt_errstr(status)); goto close_service; } if (!W_ERROR_IS_OK(werr)) { DBG_WARNING("dcerpc_svcctl_QueryServiceStatus " "failed: %s\n", win_errstr(werr)); status = werror_to_ntstatus(werr); goto close_service; } if (!(flags & SVC_IGNORE_INTERACTIVE)) { need_conf = !(service_status.type & SERVICE_TYPE_INTERACTIVE_PROCESS) ^ !(flags & SVC_INTERACTIVE); } if (service_status.state == SVCCTL_STOPPED) { need_start = true; } else if (need_conf) { status = dcerpc_svcctl_ControlService( rpccli->binding_handle, frame, &service_handle, SVCCTL_CONTROL_STOP, &service_status, &werr); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("dcerpc_svcctl_ControlServiceStatus " "failed: %s\n", nt_errstr(status)); goto close_service; } if (!W_ERROR_IS_OK(werr)) { DBG_WARNING("dcerpc_svcctl_ControlServiceStatus " "failed: %s\n", win_errstr(werr)); status = werror_to_ntstatus(werr); goto close_service; } do { smb_msleep(100); status = dcerpc_svcctl_QueryServiceStatus( rpccli->binding_handle, frame, &service_handle, &service_status, &werr); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("dcerpc_svcctl_QueryServiceStatus " "failed: %s\n", nt_errstr(status)); goto close_service; } if (!W_ERROR_IS_OK(werr)) { DBG_WARNING("dcerpc_svcctl_QueryServiceStatus " "failed: %s\n", win_errstr(werr)); status = werror_to_ntstatus(werr); goto close_service; } } while (service_status.state == SVCCTL_STOP_PENDING); need_start = 1; } if (need_conf) { status = dcerpc_svcctl_ChangeServiceConfigW( rpccli->binding_handle, frame, &service_handle, SERVICE_TYPE_WIN32_OWN_PROCESS | ((flags & SVC_INTERACTIVE) ? SERVICE_TYPE_INTERACTIVE_PROCESS : 0), /* type */ UINT32_MAX, /* start_type, SERVICE_NO_CHANGE */ UINT32_MAX, /* error_control, SERVICE_NO_CHANGE */ NULL, /* binary_path */ NULL, /* load_order_group */ NULL, /* tag_id */ NULL, /* dependencies */ 0, /* dwDependSize */ NULL, /* service_start_name */ NULL, /* password */ 0, /* dwPwSize */ NULL, /* display_name */ &werr); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("dcerpc_svcctl_ChangeServiceConfigW " "failed: %s\n", nt_errstr(status)); goto close_service; } if (!W_ERROR_IS_OK(werr)) { DBG_WARNING("dcerpc_svcctl_ChangeServiceConfigW " "failed: %s\n", win_errstr(werr)); status = werror_to_ntstatus(werr); goto close_service; } } if (need_start) { status = winexe_svc_upload( hostname, port, service_filename, svc32_exe, svc64_exe, credentials, flags); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("winexe_svc_upload failed: %s\n", nt_errstr(status)); goto close_service; } status = dcerpc_svcctl_StartServiceW( rpccli->binding_handle, frame, &service_handle, 0, /* num_args */ NULL, /* arguments */ &werr); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("dcerpc_svcctl_StartServiceW " "failed: %s\n", nt_errstr(status)); goto close_service; } if (!W_ERROR_IS_OK(werr)) { DBG_WARNING("dcerpc_svcctl_StartServiceW " "failed: %s\n", win_errstr(werr)); status = werror_to_ntstatus(werr); goto close_service; } do { smb_msleep(100); status = dcerpc_svcctl_QueryServiceStatus( rpccli->binding_handle, frame, &service_handle, &service_status, &werr); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("dcerpc_svcctl_QueryServiceStatus " "failed: %s\n", nt_errstr(status)); goto close_service; } if (!W_ERROR_IS_OK(werr)) { DBG_WARNING("dcerpc_svcctl_QueryServiceStatus " "failed: %s\n", win_errstr(werr)); status = werror_to_ntstatus(werr); goto close_service; } } while (service_status.state == SVCCTL_START_PENDING); if (service_status.state != SVCCTL_RUNNING) { DBG_WARNING("Failed to start service\n"); status = NT_STATUS_UNSUCCESSFUL; goto close_service; } } close_service: { NTSTATUS close_status; WERROR close_werr; close_status = dcerpc_svcctl_CloseServiceHandle( rpccli->binding_handle, frame, &service_handle, &close_werr); if (!NT_STATUS_IS_OK(close_status)) { DBG_WARNING("dcerpc_svcctl_CloseServiceHandle " "failed: %s\n", nt_errstr(close_status)); goto done; } if (!W_ERROR_IS_OK(close_werr)) { DBG_WARNING("dcerpc_svcctl_CloseServiceHandle " " failed: %s\n", win_errstr(close_werr)); goto done; } } close_scmanager: { NTSTATUS close_status; WERROR close_werr; close_status = dcerpc_svcctl_CloseServiceHandle( rpccli->binding_handle, frame, &scmanager_handle, &close_werr); if (!NT_STATUS_IS_OK(close_status)) { DBG_WARNING("dcerpc_svcctl_CloseServiceHandle " "failed: %s\n", nt_errstr(close_status)); goto done; } if (!W_ERROR_IS_OK(close_werr)) { DBG_WARNING("dcerpc_svcctl_CloseServiceHandle " " failed: %s\n", win_errstr(close_werr)); goto done; } } done: TALLOC_FREE(rpccli); TALLOC_FREE(frame); return status; } static NTSTATUS winexe_svc_uninstall( struct cli_state *cli, const char *service_name) { TALLOC_CTX *frame = talloc_stackframe(); struct rpc_pipe_client *rpccli; struct policy_handle scmanager_handle; struct policy_handle service_handle; struct SERVICE_STATUS service_status; NTSTATUS status; WERROR werr; const char *remote_name = smbXcli_conn_remote_name(cli->conn); const struct sockaddr_storage *remote_sockaddr = smbXcli_conn_remote_sockaddr(cli->conn); status = cli_rpc_pipe_open_noauth_transport( cli, NCACN_NP, &ndr_table_svcctl, remote_name, remote_sockaddr, &rpccli); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("cli_rpc_pipe_open_noauth_transport failed: %s\n", nt_errstr(status)); goto done; } status = dcerpc_svcctl_OpenSCManagerW( rpccli->binding_handle, frame, remote_name, NULL, SEC_FLAG_MAXIMUM_ALLOWED, &scmanager_handle, &werr); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("dcerpc_svcctl_OpenSCManagerW failed: %s\n", nt_errstr(status)); goto done; } if (!W_ERROR_IS_OK(werr)) { DBG_WARNING("dcerpc_svcctl_OpenSCManagerW failed: %s\n", win_errstr(werr)); goto done; } status = dcerpc_svcctl_OpenServiceW( rpccli->binding_handle, frame, &scmanager_handle, service_name, SERVICE_ALL_ACCESS, &service_handle, &werr); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("dcerpc_svcctl_OpenServiceW failed: %s\n", nt_errstr(status)); goto close_scmanager; } if (!W_ERROR_IS_OK(werr)) { DBG_WARNING("dcerpc_svcctl_OpenServiceW failed: %s\n", win_errstr(werr)); status = werror_to_ntstatus(werr); goto close_scmanager; } status = dcerpc_svcctl_ControlService( rpccli->binding_handle, frame, &service_handle, SVCCTL_CONTROL_STOP, &service_status, &werr); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("dcerpc_svcctl_ControlServiceStatus " "failed: %s\n", nt_errstr(status)); goto close_service; } if (!W_ERROR_IS_OK(werr)) { DBG_WARNING("dcerpc_svcctl_ControlServiceStatus " "failed: %s\n", win_errstr(werr)); status = werror_to_ntstatus(werr); goto close_service; } do { smb_msleep(100); status = dcerpc_svcctl_QueryServiceStatus( rpccli->binding_handle, frame, &service_handle, &service_status, &werr); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("dcerpc_svcctl_QueryServiceStatus " "failed: %s\n", nt_errstr(status)); goto close_service; } if (!W_ERROR_IS_OK(werr)) { DBG_WARNING("dcerpc_svcctl_QueryServiceStatus " "failed: %s\n", win_errstr(werr)); status = werror_to_ntstatus(werr); goto close_service; } } while (service_status.state != SVCCTL_STOPPED); status = dcerpc_svcctl_DeleteService( rpccli->binding_handle, frame, &service_handle, &werr); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("dcerpc_svcctl_DeleteService " "failed: %s\n", nt_errstr(status)); goto close_service; } if (!W_ERROR_IS_OK(werr)) { DBG_WARNING("dcerpc_svcctl_DeleteService " "failed: %s\n", win_errstr(werr)); status = werror_to_ntstatus(werr); goto close_service; } close_service: { NTSTATUS close_status; WERROR close_werr; close_status = dcerpc_svcctl_CloseServiceHandle( rpccli->binding_handle, frame, &service_handle, &close_werr); if (!NT_STATUS_IS_OK(close_status)) { DBG_WARNING("dcerpc_svcctl_CloseServiceHandle " "failed: %s\n", nt_errstr(close_status)); goto done; } if (!W_ERROR_IS_OK(close_werr)) { DBG_WARNING("dcerpc_svcctl_CloseServiceHandle " " failed: %s\n", win_errstr(close_werr)); goto done; } } close_scmanager: { NTSTATUS close_status; WERROR close_werr; close_status = dcerpc_svcctl_CloseServiceHandle( rpccli->binding_handle, frame, &scmanager_handle, &close_werr); if (!NT_STATUS_IS_OK(close_status)) { DBG_WARNING("dcerpc_svcctl_CloseServiceHandle " "failed: %s\n", nt_errstr(close_status)); goto done; } if (!W_ERROR_IS_OK(close_werr)) { DBG_WARNING("dcerpc_svcctl_CloseServiceHandle " " failed: %s\n", win_errstr(close_werr)); goto done; } } done: TALLOC_FREE(rpccli); TALLOC_FREE(frame); return status; } struct winexe_out_pipe_state { struct tevent_context *ev; struct cli_state *cli; uint16_t out_pipe; int out_fd; char out_inbuf[256]; }; static void winexe_out_pipe_opened(struct tevent_req *subreq); static void winexe_out_pipe_got_data(struct tevent_req *subreq); static void winexe_out_pipe_closed(struct tevent_req *subreq); static struct tevent_req *winexe_out_pipe_send( TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli, const char *pipe_name, int out_fd) { struct tevent_req *req, *subreq; struct winexe_out_pipe_state *state; req = tevent_req_create(mem_ctx, &state, struct winexe_out_pipe_state); if (req == NULL) { return NULL; } state->ev = ev; state->cli = cli; state->out_fd = out_fd; subreq = cli_ntcreate_send( state, state->ev, state->cli, pipe_name, 0, SEC_RIGHTS_FILE_READ|SEC_RIGHTS_FILE_WRITE| SEC_RIGHTS_FILE_EXECUTE, 0, /* FileAttributes */ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, FILE_OPEN, /* CreateDisposition */ 0, /* CreateOptions */ SMB2_IMPERSONATION_IMPERSONATION, 0); /* SecurityFlags */ if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, winexe_out_pipe_opened, req); return req; } static void winexe_out_pipe_opened(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct winexe_out_pipe_state *state = tevent_req_data( req, struct winexe_out_pipe_state); int timeout; NTSTATUS status; status = cli_ntcreate_recv(subreq, &state->out_pipe, NULL); TALLOC_FREE(subreq); if (tevent_req_nterror(req, status)) { return; } timeout = state->cli->timeout; state->cli->timeout = 0; subreq = cli_read_send( state, state->ev, state->cli, state->out_pipe, state->out_inbuf, 0, sizeof(state->out_inbuf)); state->cli->timeout = timeout; if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, winexe_out_pipe_got_data, req); } static void winexe_out_pipe_got_data(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct winexe_out_pipe_state *state = tevent_req_data( req, struct winexe_out_pipe_state); NTSTATUS status; int timeout; size_t received; ssize_t written; status = cli_read_recv(subreq, &received); TALLOC_FREE(subreq); DBG_DEBUG("cli_read for %d gave %s\n", state->out_fd, nt_errstr(status)); if (NT_STATUS_EQUAL(status, NT_STATUS_PIPE_DISCONNECTED)) { subreq = cli_close_send( state, state->ev, state->cli, state->out_pipe); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, winexe_out_pipe_closed, req); return; } if (tevent_req_nterror(req, status)) { return; } if (received > 0) { written = sys_write(state->out_fd, state->out_inbuf, received); if (written == -1) { tevent_req_nterror(req, map_nt_error_from_unix(errno)); return; } } timeout = state->cli->timeout; state->cli->timeout = 0; subreq = cli_read_send( state, state->ev, state->cli, state->out_pipe, state->out_inbuf, 0, sizeof(state->out_inbuf)); state->cli->timeout = timeout; if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, winexe_out_pipe_got_data, req); } static void winexe_out_pipe_closed(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); NTSTATUS status; status = cli_close_recv(subreq); TALLOC_FREE(subreq); if (tevent_req_nterror(req, status)) { return; } tevent_req_done(req); } static NTSTATUS winexe_out_pipe_recv(struct tevent_req *req) { return tevent_req_simple_recv_ntstatus(req); } struct winexe_in_pipe_state { struct tevent_context *ev; struct cli_state *cli; struct tevent_req *fd_read_req; bool close_requested; bool closing; uint16_t in_pipe; int in_fd; char inbuf[256]; }; static void winexe_in_pipe_opened(struct tevent_req *subreq); static void winexe_in_pipe_got_data(struct tevent_req *subreq); static void winexe_in_pipe_written(struct tevent_req *subreq); static void winexe_in_pipe_closed(struct tevent_req *subreq); static struct tevent_req *winexe_in_pipe_send( TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli, const char *pipe_name, int in_fd) { struct tevent_req *req, *subreq; struct winexe_in_pipe_state *state; req = tevent_req_create(mem_ctx, &state, struct winexe_in_pipe_state); if (req == NULL) { return NULL; } state->ev = ev; state->cli = cli; state->in_fd = in_fd; subreq = cli_ntcreate_send( state, state->ev, state->cli, pipe_name, 0, SEC_RIGHTS_FILE_READ|SEC_RIGHTS_FILE_WRITE| SEC_RIGHTS_FILE_EXECUTE, 0, /* FileAttributes */ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, FILE_OPEN, /* CreateDisposition */ 0, /* CreateOptions */ SMB2_IMPERSONATION_IMPERSONATION, 0); /* SecurityFlags */ if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, winexe_in_pipe_opened, req); return req; } static void winexe_in_pipe_opened(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct winexe_in_pipe_state *state = tevent_req_data( req, struct winexe_in_pipe_state); NTSTATUS status; status = cli_ntcreate_recv(subreq, &state->in_pipe, NULL); TALLOC_FREE(subreq); if (tevent_req_nterror(req, status)) { return; } subreq = wait_for_read_send( state, state->ev, state->in_fd, true); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, winexe_in_pipe_got_data, req); state->fd_read_req = subreq; } static void winexe_in_pipe_got_data(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct winexe_in_pipe_state *state = tevent_req_data( req, struct winexe_in_pipe_state); int err; bool ok; int timeout; ssize_t nread; ok = wait_for_read_recv(subreq, &err); TALLOC_FREE(subreq); if (!ok) { tevent_req_nterror(req, map_nt_error_from_unix(err)); return; } state->fd_read_req = NULL; nread = sys_read(state->in_fd, &state->inbuf, sizeof(state->inbuf)); if (nread == -1) { tevent_req_nterror(req, map_nt_error_from_unix(errno)); return; } if (nread == 0) { tevent_req_nterror(req, NT_STATUS_CONNECTION_DISCONNECTED); return; } timeout = state->cli->timeout; state->cli->timeout = 0; subreq = cli_writeall_send( state, state->ev, state->cli, state->in_pipe, 0, (uint8_t *)state->inbuf, 0, nread); state->cli->timeout = timeout; if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, winexe_in_pipe_written, req); } static void winexe_in_pipe_written(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct winexe_in_pipe_state *state = tevent_req_data( req, struct winexe_in_pipe_state); NTSTATUS status; status = cli_writeall_recv(subreq, NULL); TALLOC_FREE(subreq); DBG_DEBUG("cli_writeall for %d gave %s\n", state->in_fd, nt_errstr(status)); if (NT_STATUS_EQUAL(status, NT_STATUS_PIPE_DISCONNECTED) || state->close_requested) { subreq = cli_close_send( state, state->ev, state->cli, state->in_pipe); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, winexe_in_pipe_closed, req); state->closing = true; return; } if (tevent_req_nterror(req, status)) { return; } subreq = wait_for_read_send( state, state->ev, state->in_fd, true); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, winexe_in_pipe_got_data, req); state->fd_read_req = subreq; } static void winexe_in_pipe_closed(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); NTSTATUS status; status = cli_close_recv(subreq); TALLOC_FREE(subreq); if (tevent_req_nterror(req, status)) { return; } return tevent_req_done(req); } static NTSTATUS winexe_in_pipe_recv(struct tevent_req *req) { return tevent_req_simple_recv_ntstatus(req); } static bool winexe_in_pipe_close(struct tevent_req *req) { struct winexe_in_pipe_state *state = tevent_req_data( req, struct winexe_in_pipe_state); struct tevent_req *subreq; if (state->closing) { return true; } if (state->fd_read_req == NULL) { /* * cli_writeall active, wait for it to return */ state->close_requested = true; return true; } TALLOC_FREE(state->fd_read_req); subreq = cli_close_send( state, state->ev, state->cli, state->in_pipe); if (subreq == NULL) { return false; } tevent_req_set_callback(subreq, winexe_in_pipe_closed, req); state->closing = true; return true; } struct winexe_pipes_state { struct tevent_req *pipes[3]; }; static void winexe_pipes_stdin_done(struct tevent_req *subreq); static void winexe_pipes_stdout_done(struct tevent_req *subreq); static void winexe_pipes_stderr_done(struct tevent_req *subreq); static struct tevent_req *winexe_pipes_send( TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli, const char *pipe_postfix) { struct tevent_req *req; struct winexe_pipes_state *state; char *pipe_name; req = tevent_req_create(mem_ctx, &state, struct winexe_pipes_state); if (req == NULL) { return NULL; } pipe_name = talloc_asprintf(state, "\\ahexec_stdin%s", pipe_postfix); if (tevent_req_nomem(pipe_name, req)) { return tevent_req_post(req, ev); } state->pipes[0] = winexe_in_pipe_send( state, ev, cli, pipe_name, 0); if (tevent_req_nomem(state->pipes[0], req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(state->pipes[0], winexe_pipes_stdin_done, req); pipe_name = talloc_asprintf(state, "\\ahexec_stdout%s", pipe_postfix); if (tevent_req_nomem(pipe_name, req)) { return tevent_req_post(req, ev); } state->pipes[1] = winexe_out_pipe_send( state, ev, cli, pipe_name, 1); if (tevent_req_nomem(state->pipes[1], req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(state->pipes[1], winexe_pipes_stdout_done, req); pipe_name = talloc_asprintf(state, "\\ahexec_stderr%s", pipe_postfix); if (tevent_req_nomem(pipe_name, req)) { return tevent_req_post(req, ev); } state->pipes[2] = winexe_out_pipe_send( state, ev, cli, pipe_name, 2); if (tevent_req_nomem(state->pipes[2], req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(state->pipes[2], winexe_pipes_stderr_done, req); DBG_DEBUG("pipes = %p %p %p\n", state->pipes[0], state->pipes[1], state->pipes[2]); return req; } static void winexe_pipes_stdin_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct winexe_pipes_state *state = tevent_req_data( req, struct winexe_pipes_state); NTSTATUS status; status = winexe_in_pipe_recv(subreq); TALLOC_FREE(subreq); DBG_DEBUG("stdin returned %s\n", nt_errstr(status)); if (tevent_req_nterror(req, status)) { return; } state->pipes[0] = NULL; DBG_DEBUG("pipes = %p %p %p\n", state->pipes[0], state->pipes[1], state->pipes[2]); if ((state->pipes[1] == NULL) && (state->pipes[2] == NULL)) { tevent_req_done(req); } } static void winexe_pipes_stdout_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct winexe_pipes_state *state = tevent_req_data( req, struct winexe_pipes_state); NTSTATUS status; status = winexe_out_pipe_recv(subreq); TALLOC_FREE(subreq); DBG_DEBUG("stdout returned %s\n", nt_errstr(status)); if (tevent_req_nterror(req, status)) { return; } if (state->pipes[0] != NULL) { winexe_in_pipe_close(state->pipes[0]); } state->pipes[1] = NULL; DBG_DEBUG("pipes = %p %p %p\n", state->pipes[0], state->pipes[1], state->pipes[2]); if ((state->pipes[0] == NULL) && (state->pipes[2] == NULL)) { tevent_req_done(req); } } static void winexe_pipes_stderr_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct winexe_pipes_state *state = tevent_req_data( req, struct winexe_pipes_state); NTSTATUS status; status = winexe_out_pipe_recv(subreq); TALLOC_FREE(subreq); DBG_DEBUG("stderr returned %s\n", nt_errstr(status)); if (tevent_req_nterror(req, status)) { return; } if (state->pipes[0] != NULL) { winexe_in_pipe_close(state->pipes[0]); } state->pipes[2] = NULL; DBG_DEBUG("pipes = %p %p %p\n", state->pipes[0], state->pipes[1], state->pipes[2]); if ((state->pipes[0] == NULL) && (state->pipes[1] == NULL)) { tevent_req_done(req); } } static NTSTATUS winexe_pipes_recv(struct tevent_req *req) { return tevent_req_simple_recv_ntstatus(req); } struct winexe_ctrl_state { struct tevent_context *ev; struct cli_state *cli; uint16_t ctrl_pipe; bool ctrl_pipe_done; char ctrl_inbuf[256]; char *cmd; int return_code; struct tevent_req *pipes_req; }; static void winexe_ctrl_opened(struct tevent_req *subreq); static void winexe_ctrl_got_read(struct tevent_req *subreq); static void winexe_ctrl_wrote_version(struct tevent_req *subreq); static void winexe_ctrl_wrote_cmd(struct tevent_req *subreq); static void winexe_ctrl_pipes_done(struct tevent_req *subreq); static void winexe_ctrl_pipe_closed(struct tevent_req *subreq); static struct tevent_req *winexe_ctrl_send( TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli, const char *cmd) { struct tevent_req *req, *subreq; struct winexe_ctrl_state *state; req = tevent_req_create(mem_ctx, &state, struct winexe_ctrl_state); if (req == NULL) { return NULL; } state->ev = ev; state->cli = cli; state->cmd = talloc_asprintf(state, "run %s\n", cmd); if (tevent_req_nomem(state->cmd, req)) { return tevent_req_post(req, ev); } subreq = cli_ntcreate_send( state, state->ev, state->cli, "\\" PIPE_NAME, 0, SEC_RIGHTS_FILE_READ|SEC_RIGHTS_FILE_WRITE| SEC_RIGHTS_FILE_EXECUTE, 0, /* FileAttributes */ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, FILE_OPEN, /* CreateDisposition */ 0, /* CreateOptions */ SMB2_IMPERSONATION_IMPERSONATION, 0); /* SecurityFlags */ if (tevent_req_nomem(subreq, req)) { return tevent_req_post(req, ev); } tevent_req_set_callback(subreq, winexe_ctrl_opened, req); return req; } static void winexe_ctrl_opened(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct winexe_ctrl_state *state = tevent_req_data( req, struct winexe_ctrl_state); int timeout; NTSTATUS status; static const char cmd[] = "get codepage\nget version\n"; status = cli_ntcreate_recv(subreq, &state->ctrl_pipe, NULL); TALLOC_FREE(subreq); if (tevent_req_nterror(req, status)) { return; } timeout = state->cli->timeout; state->cli->timeout = 0; subreq = cli_read_send( state, state->ev, state->cli, state->ctrl_pipe, state->ctrl_inbuf, 0, sizeof(state->ctrl_inbuf)-1); state->cli->timeout = timeout; if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, winexe_ctrl_got_read, req); subreq = cli_writeall_send( state, state->ev, state->cli, state->ctrl_pipe, 0, (const uint8_t *)cmd, 0, strlen(cmd)); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, winexe_ctrl_wrote_version, req); } static void winexe_ctrl_got_read(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct winexe_ctrl_state *state = tevent_req_data( req, struct winexe_ctrl_state); NTSTATUS status; int timeout; size_t received; unsigned int version, return_code; int ret; status = cli_read_recv(subreq, &received); TALLOC_FREE(subreq); if (NT_STATUS_EQUAL(status, NT_STATUS_PIPE_DISCONNECTED)) { subreq = cli_close_send( state, state->ev, state->cli, state->ctrl_pipe); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, winexe_ctrl_pipe_closed, req); return; } if (tevent_req_nterror(req, status)) { return; } DBG_DEBUG("Got %zu bytes\n", received); timeout = state->cli->timeout; state->cli->timeout = 0; subreq = cli_read_send( state, state->ev, state->cli, state->ctrl_pipe, state->ctrl_inbuf, 0, sizeof(state->ctrl_inbuf)-1); state->cli->timeout = timeout; if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, winexe_ctrl_got_read, req); ret = sscanf(state->ctrl_inbuf, "version 0x%x\n", &version); if (ret == 1) { DBG_DEBUG("Got version %x\n", version); subreq = cli_writeall_send( state, state->ev, state->cli, state->ctrl_pipe, 0, (const uint8_t *)state->cmd, 0, strlen(state->cmd)); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, winexe_ctrl_wrote_cmd, req); return; } ret = strncmp(state->ctrl_inbuf, "std_io_err ", strlen("std_io_err ")); if (ret == 0) { char *p = state->ctrl_inbuf + 11; char *q = strchr(state->ctrl_inbuf, '\n'); char *postfix; size_t postfix_len; if (q == NULL) { DBG_DEBUG("Got invalid pipe postfix\n"); return; } postfix_len = q - p; postfix = talloc_strndup(state, p, postfix_len); if (tevent_req_nomem(postfix, req)) { return; } DBG_DEBUG("Got pipe postfix %s\n", postfix); subreq = winexe_pipes_send( state, state->ev, state->cli, postfix); if (tevent_req_nomem(subreq, req)) { return; } tevent_req_set_callback(subreq, winexe_ctrl_pipes_done, req); state->pipes_req = subreq; return; } ret = strncmp(state->ctrl_inbuf, "error ", strlen("error ")); if (ret == 0) { printf("Error: %s", state->ctrl_inbuf); return; } ret = sscanf(state->ctrl_inbuf, "return_code %x\n", &return_code); if (ret == 1) { state->return_code = return_code; return; } } static void winexe_ctrl_wrote_version(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); NTSTATUS status; status = cli_writeall_recv(subreq, NULL); TALLOC_FREE(subreq); if (tevent_req_nterror(req, status)) { return; } } static void winexe_ctrl_wrote_cmd(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); NTSTATUS status; status = cli_writeall_recv(subreq, NULL); TALLOC_FREE(subreq); if (tevent_req_nterror(req, status)) { return; } } static void winexe_ctrl_pipe_closed(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct winexe_ctrl_state *state = tevent_req_data( req, struct winexe_ctrl_state); NTSTATUS status; status = cli_close_recv(subreq); TALLOC_FREE(subreq); if (tevent_req_nterror(req, status)) { return; } state->ctrl_pipe_done = true; if (state->pipes_req == NULL) { tevent_req_done(req); } } static void winexe_ctrl_pipes_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); struct winexe_ctrl_state *state = tevent_req_data( req, struct winexe_ctrl_state); NTSTATUS status; status = winexe_pipes_recv(subreq); TALLOC_FREE(subreq); if (tevent_req_nterror(req, status)) { return; } state->pipes_req = NULL; if (state->ctrl_pipe_done) { tevent_req_done(req); } } static NTSTATUS winexe_ctrl_recv(struct tevent_req *req, int *preturn_code) { struct winexe_ctrl_state *state = tevent_req_data( req, struct winexe_ctrl_state); NTSTATUS status; if (tevent_req_is_nterror(req, &status)) { return status; } if (preturn_code != NULL) { *preturn_code = state->return_code; } return NT_STATUS_OK; } static NTSTATUS winexe_ctrl(struct cli_state *cli, const char *cmd, int *preturn_code) { struct tevent_context *ev = NULL; struct tevent_req *req = NULL; NTSTATUS status = NT_STATUS_NO_MEMORY; bool ok; ev = samba_tevent_context_init(cli); if (ev == NULL) { goto done; } req = winexe_ctrl_send(ev, ev, cli, cmd); if (req == NULL) { goto done; } ok = tevent_req_poll_ntstatus(req, ev, &status); if (!ok) { goto done; } status = winexe_ctrl_recv(req, preturn_code); done: TALLOC_FREE(req); TALLOC_FREE(ev); return status; } #ifdef HAVE_WINEXE_CC_WIN32 const DATA_BLOB *winexesvc32_exe_binary(void); #endif #ifdef HAVE_WINEXE_CC_WIN64 const DATA_BLOB *winexesvc64_exe_binary(void); #endif int main(int argc, char *argv[]) { TALLOC_CTX *frame = talloc_stackframe(); const char **const_argv = discard_const_p(const char *, argv); struct program_options options = {0}; struct cli_state *cli = NULL; const char *service_name = SERVICE_NAME; char *service_filename = NULL; #ifdef HAVE_WINEXE_CC_WIN32 const DATA_BLOB *winexesvc32_exe = winexesvc32_exe_binary(); #else const DATA_BLOB *winexesvc32_exe = NULL; #endif #ifdef HAVE_WINEXE_CC_WIN64 const DATA_BLOB *winexesvc64_exe = winexesvc64_exe_binary(); #else const DATA_BLOB *winexesvc64_exe = NULL; #endif NTSTATUS status; int ret = 1; int return_code = 0; smb_init_locale(); parse_args(argc, const_argv, frame, &options); samba_cmdline_burn(argc, argv); if (options.cmd == NULL) { fprintf(stderr, "no cmd given\n"); goto done; } service_filename = talloc_asprintf(frame, "%s.exe", service_name); if (service_filename == NULL) { DBG_WARNING("talloc_asprintf failed\n"); goto done; } status = cli_full_connection_creds( &cli, lp_netbios_name(), options.hostname, NULL, options.port, "IPC$", "IPC", options.credentials, CLI_FULL_CONNECTION_IPC); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("cli_full_connection_creds failed: %s\n", nt_errstr(status)); goto done; } status = winexe_svc_install( cli, options.hostname, options.port, service_name, service_filename, winexesvc32_exe, winexesvc64_exe, options.credentials, options.flags); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("winexe_svc_install failed: %s\n", nt_errstr(status)); goto done; } status = winexe_ctrl(cli, options.cmd, &return_code); if (NT_STATUS_EQUAL(status, NT_STATUS_PIPE_DISCONNECTED)) { /* Normal finish */ status = NT_STATUS_OK; } if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("cli_ctrl failed: %s\n", nt_errstr(status)); goto done; } if (options.flags & SVC_UNINSTALL) { status = winexe_svc_uninstall( cli, service_name); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("winexe_svc_uninstall failed: %s\n", nt_errstr(status)); goto done; } } ret = return_code; done: TALLOC_FREE(frame); return ret; }