diff --git a/windows/winhandl.c b/windows/winhandl.c index 286f79a6..96988090 100644 --- a/windows/winhandl.c +++ b/windows/winhandl.c @@ -65,6 +65,8 @@ struct handle_generic { void *privdata; /* for client to remember who they are */ }; +typedef enum { INPUT, OUTPUT, FOREIGN } HandleType; + /* ---------------------------------------------------------------------- * Input threads. */ @@ -329,16 +331,44 @@ static void handle_try_output(struct handle_output *ctx) } } +/* ---------------------------------------------------------------------- + * 'Foreign events'. These are handle structures which just contain a + * single event object passed to us by another module such as + * winnps.c, so that they can make use of our handle_get_events / + * handle_got_event mechanism for communicating with application main + * loops. + */ +struct handle_foreign { + /* + * Copy of the handle_generic structure. + */ + HANDLE h; /* the handle itself */ + HANDLE ev_to_main; /* event used to signal main thread */ + HANDLE ev_from_main; /* event used to signal back to us */ + int moribund; /* are we going to kill this soon? */ + int done; /* request subthread to terminate */ + int defunct; /* has the subthread already gone? */ + int busy; /* operation currently in progress? */ + void *privdata; /* for client to remember who they are */ + + /* + * Our own data, just consisting of knowledge of who to call back. + */ + void (*callback)(void *); + void *ctx; +}; + /* ---------------------------------------------------------------------- * Unified code handling both input and output threads. */ struct handle { - int output; + HandleType type; union { struct handle_generic g; struct handle_input i; struct handle_output o; + struct handle_foreign f; } u; }; @@ -376,7 +406,7 @@ struct handle *handle_input_new(HANDLE handle, handle_inputfn_t gotdata, struct handle *h = snew(struct handle); DWORD in_threadid; /* required for Win9x */ - h->output = FALSE; + h->type = INPUT; h->u.i.h = handle; h->u.i.ev_to_main = CreateEvent(NULL, FALSE, FALSE, NULL); h->u.i.ev_from_main = CreateEvent(NULL, FALSE, FALSE, NULL); @@ -404,7 +434,7 @@ struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, struct handle *h = snew(struct handle); DWORD out_threadid; /* required for Win9x */ - h->output = TRUE; + h->type = OUTPUT; h->u.o.h = handle; h->u.o.ev_to_main = CreateEvent(NULL, FALSE, FALSE, NULL); h->u.o.ev_from_main = CreateEvent(NULL, FALSE, FALSE, NULL); @@ -428,9 +458,33 @@ struct handle *handle_output_new(HANDLE handle, handle_outputfn_t sentdata, return h; } +struct handle *handle_add_foreign_event(HANDLE event, + void (*callback)(void *), void *ctx) +{ + struct handle *h = snew(struct handle); + + h->type = FOREIGN; + h->u.f.h = INVALID_HANDLE_VALUE; + h->u.f.ev_to_main = event; + h->u.f.ev_from_main = INVALID_HANDLE_VALUE; + h->u.f.defunct = TRUE; /* we have no thread in the first place */ + h->u.f.moribund = FALSE; + h->u.f.done = FALSE; + h->u.f.privdata = NULL; + h->u.f.callback = callback; + h->u.f.ctx = ctx; + h->u.f.busy = TRUE; + + if (!handles_by_evtomain) + handles_by_evtomain = newtree234(handle_cmp_evtomain); + add234(handles_by_evtomain, h); + + return h; +} + int handle_write(struct handle *h, const void *data, int len) { - assert(h->output); + assert(h->type == OUTPUT); assert(h->u.o.outgoingeof == EOF_NO); bufchain_add(&h->u.o.queued_data, data, len); handle_try_output(&h->u.o); @@ -446,7 +500,7 @@ void handle_write_eof(struct handle *h) * bidirectional handle if we're still interested in its incoming * direction! */ - assert(h->output); + assert(h->type == OUTPUT); if (!h->u.o.outgoingeof == EOF_NO) { h->u.o.outgoingeof = EOF_PENDING; handle_try_output(&h->u.o); @@ -483,7 +537,7 @@ HANDLE *handle_get_events(int *nevents) static void handle_destroy(struct handle *h) { - if (h->output) + if (h->type == OUTPUT) bufchain_clear(&h->u.o.queued_data); CloseHandle(h->u.g.ev_from_main); CloseHandle(h->u.g.ev_to_main); @@ -560,9 +614,10 @@ void handle_got_event(HANDLE event) return; } - if (!h->output) { + switch (h->type) { int backlog; + case INPUT: h->u.i.busy = FALSE; /* @@ -578,7 +633,9 @@ void handle_got_event(HANDLE event) backlog = h->u.i.gotdata(h, h->u.i.buffer, h->u.i.len); handle_throttle(&h->u.i, backlog); } - } else { + break; + + case OUTPUT: h->u.o.busy = FALSE; /* @@ -599,18 +656,24 @@ void handle_got_event(HANDLE event) h->u.o.sentdata(h, bufchain_size(&h->u.o.queued_data)); handle_try_output(&h->u.o); } + break; + + case FOREIGN: + /* Just call the callback. */ + h->u.f.callback(h->u.f.ctx); + break; } } void handle_unthrottle(struct handle *h, int backlog) { - assert(!h->output); + assert(h->type == INPUT); handle_throttle(&h->u.i, backlog); } int handle_backlog(struct handle *h) { - assert(h->output); + assert(h->type == OUTPUT); return bufchain_size(&h->u.o.queued_data); } diff --git a/windows/winnpc.c b/windows/winnpc.c new file mode 100644 index 00000000..9b347dd3 --- /dev/null +++ b/windows/winnpc.c @@ -0,0 +1,84 @@ +/* + * Windows support module which deals with being a named-pipe client. + */ + +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "ssh.h" + +#if !defined NO_SECURITY + +#include + +Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, Plug plug, + int overlapped); + +Socket new_named_pipe_client(const char *pipename, Plug plug) +{ + HANDLE pipehandle; + PSID usersid, pipeowner; + PSECURITY_DESCRIPTOR psd; + char *err; + Socket ret; + + extern int advapi_initialised; + init_advapi(); /* for get_user_sid. FIXME: do better. */ + + assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0); + assert(strchr(pipename + 9, '\\') == NULL); + + pipehandle = CreateFile(pipename, GENERIC_READ | GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + + if (pipehandle == INVALID_HANDLE_VALUE) { + err = dupprintf("Unable to open named pipe '%s': %s", + pipename, win_strerror(GetLastError())); + ret = new_error_socket(err, plug); + sfree(err); + return ret; + } + + if ((usersid = get_user_sid()) == NULL) { + CloseHandle(pipehandle); + err = dupprintf("Unable to get user SID"); + ret = new_error_socket(err, plug); + sfree(err); + return ret; + } + + if (GetSecurityInfo(pipehandle, SE_KERNEL_OBJECT, + OWNER_SECURITY_INFORMATION, + &pipeowner, NULL, NULL, NULL, + &psd) != ERROR_SUCCESS) { + err = dupprintf("Unable to get named pipe security information: %s", + win_strerror(GetLastError())); + ret = new_error_socket(err, plug); + sfree(err); + CloseHandle(pipehandle); + sfree(usersid); + return ret; + } + + if (!EqualSid(pipeowner, usersid)) { + err = dupprintf("Owner of named pipe '%s' is not us", pipename); + ret = new_error_socket(err, plug); + sfree(err); + CloseHandle(pipehandle); + LocalFree(psd); + sfree(usersid); + return ret; + } + + LocalFree(psd); + sfree(usersid); + + return make_handle_socket(pipehandle, pipehandle, plug, TRUE); +} + +#endif /* !defined NO_SECURITY */ diff --git a/windows/winnps.c b/windows/winnps.c new file mode 100644 index 00000000..200ad62b --- /dev/null +++ b/windows/winnps.c @@ -0,0 +1,305 @@ +/* + * Windows support module which deals with being a named-pipe server. + */ + +#include +#include + +#define DEFINE_PLUG_METHOD_MACROS +#include "tree234.h" +#include "putty.h" +#include "network.h" +#include "proxy.h" +#include "ssh.h" + +#if !defined NO_SECURITY + +#include + +Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, Plug plug, + int overlapped); + +typedef struct Socket_named_pipe_server_tag *Named_Pipe_Server_Socket; +struct Socket_named_pipe_server_tag { + const struct socket_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + /* Parameters for (repeated) creation of named pipe objects */ + PSECURITY_DESCRIPTOR psd; + PSID networksid; + PACL acl; + char *pipename; + + /* The current named pipe object + attempt to connect to it */ + HANDLE pipehandle; + OVERLAPPED connect_ovl; + + /* PuTTY Socket machinery */ + Plug plug; + char *error; + void *privptr; +}; + +static Plug sk_namedpipeserver_plug(Socket s, Plug p) +{ + Named_Pipe_Server_Socket ps = (Named_Pipe_Server_Socket) s; + Plug ret = ps->plug; + if (p) + ps->plug = p; + return ret; +} + +static void sk_namedpipeserver_close(Socket s) +{ + Named_Pipe_Server_Socket ps = (Named_Pipe_Server_Socket) s; + + CloseHandle(ps->pipehandle); + CloseHandle(ps->connect_ovl.hEvent); + sfree(ps->error); + sfree(ps->pipename); + if (ps->networksid) + LocalFree(ps->networksid); + if (ps->acl) + LocalFree(ps->acl); + if (ps->psd) + LocalFree(ps->psd); + sfree(ps); +} + +static void sk_namedpipeserver_set_private_ptr(Socket s, void *ptr) +{ + Named_Pipe_Server_Socket ps = (Named_Pipe_Server_Socket) s; + ps->privptr = ptr; +} + +static void *sk_namedpipeserver_get_private_ptr(Socket s) +{ + Named_Pipe_Server_Socket ps = (Named_Pipe_Server_Socket) s; + return ps->privptr; +} + +static const char *sk_namedpipeserver_socket_error(Socket s) +{ + Named_Pipe_Server_Socket ps = (Named_Pipe_Server_Socket) s; + return ps->error; +} + +static int create_named_pipe(Named_Pipe_Server_Socket ps, int first_instance) +{ + SECURITY_ATTRIBUTES sa; + + memset(&sa, 0, sizeof(sa)); + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = ps->psd; + sa.bInheritHandle = FALSE; + + ps->pipehandle = CreateNamedPipe + (/* lpName */ + ps->pipename, + + /* dwOpenMode */ + PIPE_ACCESS_DUPLEX | + FILE_FLAG_OVERLAPPED | + (first_instance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0), + + /* dwPipeMode */ + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT +#ifdef PIPE_REJECT_REMOTE_CLIENTS + | PIPE_REJECT_REMOTE_CLIENTS +#endif + , + + /* nMaxInstances */ + PIPE_UNLIMITED_INSTANCES, + + /* nOutBufferSize, nInBufferSize */ + 4096, 4096, /* FIXME: think harder about buffer sizes? */ + + /* nDefaultTimeOut */ + 0 /* default timeout */, + + /* lpSecurityAttributes */ + &sa); + + return ps->pipehandle != INVALID_HANDLE_VALUE; +} + +static Socket named_pipe_accept(accept_ctx_t ctx, Plug plug) +{ + HANDLE conn = (HANDLE)ctx.p; + + return make_handle_socket(conn, conn, plug, TRUE); +} + +static void named_pipe_accept_loop(Named_Pipe_Server_Socket ps, + int got_one_already) +{ + while (1) { + int error; + char *errmsg; + + if (got_one_already) { + /* If we were called with a connection already waiting, + * skip this step. */ + got_one_already = FALSE; + error = 0; + } else { + /* + * Call ConnectNamedPipe, which might succeed or might + * tell us that an overlapped operation is in progress and + * we should wait for our event object. + */ + if (ConnectNamedPipe(ps->pipehandle, &ps->connect_ovl)) + error = 0; + else + error = GetLastError(); + + if (error == ERROR_IO_PENDING) + return; + } + + if (error == 0 || error == ERROR_PIPE_CONNECTED) { + /* + * We've successfully retrieved an incoming connection, so + * ps->pipehandle now refers to that connection. So + * convert that handle into a separate connection-type + * Socket, and create a fresh one to be the new listening + * pipe. + */ + HANDLE conn = ps->pipehandle; + accept_ctx_t actx; + + actx.p = (void *)conn; + if (plug_accepting(ps->plug, named_pipe_accept, actx)) { + /* + * If the plug didn't want the connection, might as + * well close this handle. + */ + CloseHandle(conn); + } + + if (!create_named_pipe(ps, FALSE)) { + error = GetLastError(); + } else { + /* + * Go round again to see if more connections can be + * got, or to begin waiting on the event object. + */ + continue; + } + } + + errmsg = dupprintf("Error while listening to named pipe: %s", + win_strerror(error)); + plug_log(ps->plug, 1, NULL /* FIXME: appropriate kind of sockaddr */, 0, + errmsg, error); + sfree(errmsg); + break; + } +} + +static void named_pipe_connect_callback(void *vps) +{ + Named_Pipe_Server_Socket ps = (Named_Pipe_Server_Socket)vps; + named_pipe_accept_loop(ps, TRUE); +} + +Socket new_named_pipe_listener(const char *pipename, Plug plug) +{ + /* + * This socket type is only used for listening, so it should never + * be asked to write or flush or set_frozen. + */ + static const struct socket_function_table socket_fn_table = { + sk_namedpipeserver_plug, + sk_namedpipeserver_close, + NULL /* write */, + NULL /* write_oob */, + NULL /* write_eof */, + NULL /* flush */, + sk_namedpipeserver_set_private_ptr, + sk_namedpipeserver_get_private_ptr, + NULL /* set_frozen */, + sk_namedpipeserver_socket_error + }; + + Named_Pipe_Server_Socket ret; + SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY; + EXPLICIT_ACCESS ea[2]; + + ret = snew(struct Socket_named_pipe_server_tag); + ret->fn = &socket_fn_table; + ret->plug = plug; + ret->error = NULL; + ret->privptr = NULL; + ret->psd = NULL; + ret->pipename = dupstr(pipename); + ret->networksid = NULL; + ret->acl = NULL; + + assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0); + assert(strchr(pipename + 9, '\\') == NULL); + + if (!AllocateAndInitializeSid(&nt_auth, 1, SECURITY_NETWORK_RID, + 0, 0, 0, 0, 0, 0, 0, &ret->networksid)) { + ret->error = dupprintf("unable to construct SID for rejecting " + "remote pipe connections: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + memset(ea, 0, sizeof(ea)); + ea[0].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE; + ea[0].grfAccessMode = GRANT_ACCESS; + ea[0].grfInheritance = NO_INHERITANCE; + ea[0].Trustee.TrusteeForm = TRUSTEE_IS_NAME; + ea[0].Trustee.ptstrName = "CURRENT_USER"; + ea[1].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE; + ea[1].grfAccessMode = REVOKE_ACCESS; + ea[1].grfInheritance = NO_INHERITANCE; + ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; + ea[1].Trustee.ptstrName = (LPTSTR)ret->networksid; + + if (SetEntriesInAcl(2, ea, NULL, &ret->acl) != ERROR_SUCCESS) { + ret->error = dupprintf("unable to construct ACL: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + ret->psd = (PSECURITY_DESCRIPTOR) + LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); + if (!ret->psd) { + ret->error = dupprintf("unable to allocate security descriptor: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + if (!InitializeSecurityDescriptor(ret->psd,SECURITY_DESCRIPTOR_REVISION)) { + ret->error = dupprintf("unable to initialise security descriptor: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + if (!SetSecurityDescriptorDacl(ret->psd, TRUE, ret->acl, FALSE)) { + ret->error = dupprintf("unable to set DACL in security descriptor: %s", + win_strerror(GetLastError())); + goto cleanup; + } + + if (!create_named_pipe(ret, TRUE)) { + ret->error = dupprintf("unable to create named pipe '%s': %s", + pipename, win_strerror(GetLastError())); + goto cleanup; + } + + memset(&ret->connect_ovl, 0, sizeof(ret->connect_ovl)); + ret->connect_ovl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + handle_add_foreign_event(ret->connect_ovl.hEvent, + named_pipe_connect_callback, ret); + named_pipe_accept_loop(ret, FALSE); + + cleanup: + return (Socket) ret; +} + +#endif /* !defined NO_SECURITY */ diff --git a/windows/winstuff.h b/windows/winstuff.h index e21773a0..54a06c07 100644 --- a/windows/winstuff.h +++ b/windows/winstuff.h @@ -501,6 +501,8 @@ void handle_got_event(HANDLE event); void handle_unthrottle(struct handle *h, int backlog); int handle_backlog(struct handle *h); void *handle_get_privdata(struct handle *h); +struct handle *handle_add_foreign_event(HANDLE event, + void (*callback)(void *), void *ctx); /* * winpgntc.c needs to schedule callbacks for asynchronous agent