1 #include <stdio.h> /* sprintf */
3 #include <limits.h> /* INT_MAX */
5 #include "cthelper/cthelper.h"
6 #include "cthelper/message.h"
8 #define CYGTERM_MAX_BACKLOG 16384
10 #define CYGTERM_NAME "Cygterm"
13 #define CTHELPER "posix.exe /u /c cthelper.exe"
15 #define CTHELPER "cthelper"
19 #define cygterm_debug(f,...)
20 #elif !defined(cygterm_debug)
21 #define cygterm_debug(f,...) debug(("%s:%d:%s: "f"\n",__FILE__,__LINE__,__FUNCTION__,##__VA_ARGS__))
24 #define putenv _putenv
26 typedef struct cygterm_backend_data
{
27 const struct plug_function_table
*fn
;
31 PROCESS_INFORMATION pi
;
40 /* Plug functions for cthelper data connection */
42 cygterm_log(Plug p
, int type
, SockAddr addr
, int port
, const char *error_msg
, int error_code
)
48 cygterm_closing(Plug plug
, const char *error_msg
, int error_code
, int calling_back
)
50 Local local
= (Local
)plug
;
56 /* check for errors from cthelper */
57 CloseHandle(local
->ctl
);
58 /* wait for cthelper */
59 if (local
->pi
.hProcess
!= INVALID_HANDLE_VALUE
) {
60 if (WAIT_OBJECT_0
== WaitForSingleObject(local
->pi
.hProcess
, 2000)) {
62 GetExitCodeProcess(local
->pi
.hProcess
, &status
);
66 case CthelperInvalidUsage
:
67 case CthelperInvalidPort
:
68 case CthelperConnectFailed
:
69 local
->exitcode
= INT_MAX
;
70 error_msg
= "Internal error";
72 case CthelperPtyforkFailure
:
73 local
->exitcode
= INT_MAX
;
74 error_msg
= "Failed to allocate pseudoterminal";
76 case CthelperExecFailure
:
77 local
->exitcode
= INT_MAX
;
78 error_msg
= "Failed to execute command";
83 /* this calls cygterm_exitcode() */
84 notify_remote_exit(local
->frontend
);
86 cygterm_debug("error_msg: %s", error_msg
);
87 connection_fatal(local
->frontend
, "%s", error_msg
);
93 cygterm_receive(Plug plug
, int urgent
, char *data
, int len
)
95 Local local
= (Local
)plug
;
97 cygterm_debug("backend -> display %u", len
);
98 // dmemdump(data, len);
99 backlog
= from_backend(local
->frontend
, 0, data
, len
);
100 // dmemdumpl(data, len);
101 sk_set_frozen(local
->s
, backlog
> CYGTERM_MAX_BACKLOG
);
107 cygterm_sent(Plug plug
, int bufsize
)
109 Local local
= (Local
)plug
;
110 local
->bufsize
= bufsize
;
114 cygterm_size(void *handle
, int width
, int height
);
117 cygterm_accepting(Plug plug
, OSSocket sock
)
119 Local local
= (Local
)plug
;
120 cygterm_debug("top");
121 local
->s
= sk_register(sock
, plug
);
122 sk_set_frozen(local
->s
, 0);
123 /* Reset terminal size */
124 cygterm_size(local
, local
->cfg
.width
, local
->cfg
.height
);
130 static char *getCygwinBin(void);
131 static void appendPath(const char *append
);
132 static size_t makeAttributes(char *buf
, Config
*cfg
);
133 static const char *spawnChild(char *cmd
, LPPROCESS_INFORMATION ppi
, PHANDLE pin
);
135 /* Backend functions for the cygterm backend */
138 cygterm_init(void *frontend_handle
, void **backend_handle
,
140 char *unused_host
, int unused_port
,
141 char **realhost
, int nodelay
, int keepalive
)
143 /* XXX: I'm not sure if it is OK to overload Plug like this.
144 * cygterm_accepting should only be used for the listening socket
145 * (local->a) while the cygterm_closing, cygterm_receive, and cygterm_sent
146 * should be used only for the actual connection (local->s).
148 static const struct plug_function_table fn_table
= {
157 char cmdline
[2 * MAX_PATH
];
162 cygterm_debug("top");
164 local
= snew(struct cygterm_backend_data
);
165 local
->fn
= &fn_table
;
172 *backend_handle
= local
;
174 local
->frontend
= frontend_handle
;
176 /* set up listen socket for communication with child */
177 cygterm_debug("setupCygTerm");
179 /* let sk use INADDR_LOOPBACK and let WinSock choose a port */
180 local
->a
= sk_newlistener(0, 0, (Plug
)local
, 1, ADDRTYPE_IPV4
);
181 if ((err
= sk_socket_error(local
->a
)) != NULL
)
184 /* now, get the port that WinSock chose */
185 /* XXX: Is there another function in PuTTY to do this? */
186 cygterm_debug("getting port");
187 cport
= sk_getport(local
->a
);
189 err
= "Failed to get port number for cthelper";
193 if (strchr(local
->cfg
.termtype
, ' ')) {
194 err
= "term type contains spaces";
198 /* Build cthelper command line */
199 cmdlinelen
= sprintf(cmdline
, CTHELPER
" %u %s ", cport
, local
->cfg
.termtype
);
200 cmdlinelen
+= makeAttributes(cmdline
+ cmdlinelen
, &local
->cfg
);
202 command
= cfg
->cygcmd
;
203 cygterm_debug("command is :%s:", command
);
204 /* A command of "." or "-" tells us to pass no command arguments to
205 * cthelper which will then run the user's shell under Cygwin. */
206 if ((command
[0]=='-'||command
[0]=='.') && command
[1]=='\0')
208 else if (cmdlinelen
+ strlen(command
) + 2 > sizeof cmdline
) {
209 err
= "command is too long";
213 cmdlinelen
+= sprintf(cmdline
+ cmdlinelen
, " %s", command
);
216 /* Add the Cygwin /bin path to the PATH. */
217 if (cfg
->cygautopath
) {
218 char *cygwinBinPath
= getCygwinBin();
219 if (!cygwinBinPath
) {
220 /* we'll try anyway */
221 cygterm_debug("cygwin bin directory not found");
224 cygterm_debug("found cygwin directory: %s", cygwinBinPath
);
225 appendPath(cygwinBinPath
);
226 sfree(cygwinBinPath
);
230 cygterm_debug("starting cthelper: %s", cmdline
);
231 if ((err
= spawnChild(cmdline
, &local
->pi
, &local
->ctl
)))
234 /* This should be set to the local hostname, Apparently, realhost is used
235 * only to set the window title.
237 strcpy(*realhost
= smalloc(sizeof CYGTERM_NAME
), CYGTERM_NAME
);
249 cygterm_free(void *handle
)
251 Local local
= handle
;
252 cygterm_debug("top");
257 cygterm_reconfig(void *handle
, Config
*cfg
)
259 Local local
= handle
;
260 cygterm_debug("top");
265 cygterm_send(void *handle
, char *buf
, int len
)
267 Local local
= handle
;
268 cygterm_debug("frontend -> pty %u", len
);
269 // dmemdump(buf, len);
274 for (i
= 0; i
< len
- 1; i
++)
275 if (buf
[i
] == 033 && !(buf
[i
+1]&0x80)) {
276 memmove(buf
+ i
, buf
+ i
+ 1, --len
- i
);
282 local
->bufsize
= sk_write(local
->s
, buf
, len
);
284 return local
->bufsize
;
288 cygterm_sendbuffer(void *handle
)
290 Local local
= handle
;
291 cygterm_debug("top");
292 return local
->bufsize
;
296 cygterm_size(void *handle
, int width
, int height
)
298 Local local
= handle
;
299 cygterm_debug("top");
300 cygterm_debug("size=%d,%d (last=%d,%d)",
301 width
, height
, local
->cfg
.width
, local
->cfg
.height
);
302 local
->cfg
.width
= width
;
303 local
->cfg
.height
= height
;
307 m
.size
= MESSAGE_MIN
+ sizeof(m
.msg
);
309 m
.msg
.resize
.width
= width
;
310 m
.msg
.resize
.height
= height
;
311 cygterm_debug("WriteFile %p %p:%u", local
->ctl
, &m
, m
.size
);
312 WriteFile(local
->ctl
, (const char *)&m
, m
.size
, &n
, 0);
313 cygterm_debug("WriteFile returns %d");
318 cygterm_special(void *handle
, Telnet_Special code
)
320 cygterm_debug("top");
323 static const struct telnet_special
*
324 cygterm_get_specials(void *handle
)
326 cygterm_debug("top");
331 cygterm_connected(void *handle
)
333 Local local
= handle
;
334 cygterm_debug("top");
335 return local
->s
!= NULL
;
339 cygterm_exitcode(void *handle
)
341 Local local
= handle
;
342 cygterm_debug("top");
343 return local
->exitcode
;
347 cygterm_sendok(void *handle
)
349 cygterm_debug("top");
354 cygterm_unthrottle(void *handle
, int backlog
)
356 Local local
= handle
;
357 cygterm_debug("top");
358 sk_set_frozen(local
->s
, backlog
> CYGTERM_MAX_BACKLOG
);
362 cygterm_ldisc(void *handle
, int option
)
364 Local local
= handle
;
365 cygterm_debug("cygterm_ldisc: %d", option
);
368 return local
->editing
;
370 return local
->echoing
;
376 cygterm_provide_ldisc(void *handle
, void *ldisc
)
378 cygterm_debug("top");
382 cygterm_provide_logctx(void *handle
, void *logctx
)
384 cygterm_debug("top");
388 cygterm_cfg_info(void *handle
)
393 Backend cygterm_backend
= {
401 cygterm_get_specials
,
406 cygterm_provide_ldisc
,
407 cygterm_provide_logctx
,
414 /* like strcpy(), but return pointer to terminating null character */
416 strecpy(char *d
,const char *s
)
423 /* Make cthelper attribute string from PuTTY Config */
425 makeAttributes(char *buf
, Config
*cfg
)
429 if (cfg
->bksp_is_delete
)
430 e
= strecpy(e
, ":erase=^?");
432 e
= strecpy(e
, ":erase=^H");
434 e
+= sprintf(e
, ":size=%d,%d", cfg
->height
, cfg
->width
);
436 /* TODO: other options? localedit? localecho? */
442 /* Utility functions for spawning cthelper process */
444 getRegistry(char *valueData
, LPDWORD psize
, HKEY key
, const char *subKey
, const char *valueName
)
449 if (ERROR_SUCCESS
!= (ret
= RegOpenKey(key
, subKey
, &k
)))
452 ERROR_SUCCESS
== (ret
= RegQueryInfoKey(k
, 0, 0, 0, 0, 0, 0, 0, 0, psize
, 0, 0))
453 && ERROR_SUCCESS
== (ret
= RegQueryValueEx(k
, valueName
, 0, 0, valueData
, psize
));
459 /* As of Cygwin 1.7, one of these keys contains the Cygwin install root. */
460 #define CYGWIN_U_SETUP_ROOTDIR \
462 "Software\\Cygwin\\setup",\
464 #define CYGWIN_S_SETUP_ROOTDIR \
466 "Software\\Cygwin\\setup",\
473 DWORD size
= MAX_PATH
;
478 if (ERROR_SUCCESS
== getRegistry(dir
, &size
, CYGWIN_U_SETUP_ROOTDIR
)
479 || ERROR_SUCCESS
== getRegistry(dir
, &size
, CYGWIN_S_SETUP_ROOTDIR
))
481 strcat(dir
, "\\bin");
493 appendPath(const char *append
)
498 cygterm_debug("getting PATH");
499 if (!(path
= getenv("PATH"))) {
500 cygterm_debug("hmm.. PATH not set");
503 cygterm_debug("alloc newPath");
504 newPath
= smalloc(5 + strlen(append
) + 1 + strlen(path
) + 1);
505 cygterm_debug("init newPath");
506 sprintf(newPath
, "PATH=%s;%s", path
, append
);
507 cygterm_debug("set newPath");
509 cygterm_debug("free newPath");
514 spawnChild(char *cmd
, LPPROCESS_INFORMATION ppi
, PHANDLE pin
)
516 STARTUPINFO si
= {sizeof(si
)};
517 SECURITY_ATTRIBUTES sa
= {sizeof(sa
)};
520 /* Create an anonymous pipe over which to send events such as resize */
521 sa
.lpSecurityDescriptor
= NULL
;
522 sa
.bInheritHandle
= TRUE
;
523 if (!CreatePipe(&in
, pin
, &sa
, 1))
524 return "failed to create event pipe";
526 /* cthelper will use stdin to get event messages */
527 si
.dwFlags
= STARTF_USESTDHANDLES
|STARTF_USESHOWWINDOW
;
529 si
.wShowWindow
= SW_HIDE
;
531 /* We allow cthelper to inherit handles. I have no idea if there are
532 * other inheritable handles in PuTTY that this will effect. cthelper
533 * will attempt to close all open descriptors.
536 NULL
, cmd
, /* command line */
537 NULL
, NULL
, /* no process or thread security attributes */
538 TRUE
, /* inherit handles */
539 CREATE_NEW_CONSOLE
, /* create a new console window */
540 NULL
, /* use parent environment */
541 0, /* use parent working directory */
542 &si
, /* STARTUPINFO sets stdin to read end of pipe */
547 *pin
= INVALID_HANDLE_VALUE
;
548 return "failed to run cthelper";
551 /* close the read end of the pipe */
557 /* ex:set ts=4 sw=4: */