1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/* --------------------------------------------------------------------
18 *
19 * wintty : a Apache/WinNT support utility for monitoring and
20 *          reflecting user feedback from the Apache process via
21 *          stdin/stdout, even as running within the service context.
22 *
23 * Originally contributed by William Rowe <wrowe covalent.net>
24 *
25 * Note: this implementation is _very_ experimental, and error handling
26 * is far from complete.  Using it as a cgi or pipe process allows the
27 * programmer to discover if facilities such as reliable piped logs
28 * are working as expected, or answer operator prompts that would
29 * otherwise be discarded by the service process.
30 *
31 * Also note the isservice detection semantics, which far exceed any
32 * mechanism we have discovered thus far.
33 *
34 * --------------------------------------------------------------------
35 */
36
37#define WIN32_LEAN_AND_MEAN
38#include <windows.h>
39#include <stdlib.h>
40#include <stdio.h>
41
42#if defined(_MSC_VER) && _MSC_VER >= 1400
43#define _CRT_SECURE_NO_DEPRECATE
44#pragma warning(disable: 4996)
45#endif
46
47const char *options =
48"\nwintty: a utility for echoing the stdin stream to a new console window,\n"
49"\teven when invoked from within a service (such as the Apache server.)\n"
50"\tAlso reflects the console input back to the stdout stream, allowing\n"
51"\tthe operator to respond to prompts from the context of a service.\n\n"
52"Syntax: %s [opts] [-t \"Window Title\"]\n\n"
53"  opts: -c{haracter}   or -l{ine} input\n"
54"\t-q{uiet}       or -e{cho} input\n"
55"\t-u{nprocessed} or -p{rocessed} input\n"
56"\t-n{owrap}      or -w{rap} output lines\n"
57"\t-f{ormatted}   or -r{aw} output lines\n"
58"\t-O{output} [number of seconds]\n"
59"\t-v{erbose} error reporting (for debugging)\n"
60"\t-? for this message\n\n";
61
62BOOL verbose = FALSE;
63
64void printerr(char *fmt, ...)
65{
66    char str[1024];
67    va_list args;
68    if (!verbose)
69        return;
70    va_start(args, fmt);
71    wvsprintf(str, fmt, args);
72    OutputDebugString(str);
73}
74
75DWORD WINAPI feedback(LPVOID args);
76
77typedef struct feedback_args_t {
78    HANDLE in;
79    HANDLE out;
80} feedback_args_t;
81
82int main(int argc, char** argv)
83{
84    char str[1024], *contitle = NULL;
85    HANDLE hproc, thread;
86    HANDLE hwinsta = NULL, hsavewinsta;
87    HANDLE hdesk = NULL, hsavedesk = NULL;
88    HANDLE conin, conout;
89    HANDLE hstdin, hstdout, hstderr, hdup;
90    feedback_args_t feed;
91    DWORD conmode;
92    DWORD newinmode = 0, notinmode = 0;
93    DWORD newoutmode = 0, notoutmode = 0;
94    DWORD tid;
95    DWORD len;
96    DWORD timeout = INFINITE;
97    BOOL isservice = FALSE;
98    char *arg0 = argv[0];
99
100    while (--argc) {
101        ++argv;
102        if (**argv == '/' || **argv == '-') {
103            switch (tolower((*argv)[1])) {
104                case 'c':
105                    notinmode |= ENABLE_LINE_INPUT;          break;
106                case 'l':
107                    newinmode |= ENABLE_LINE_INPUT;          break;
108                case 'q':
109                    notinmode |= ENABLE_ECHO_INPUT;          break;
110                case 'e':
111                    newinmode |= ENABLE_ECHO_INPUT;          break;
112                case 'u':
113                    notinmode |= ENABLE_PROCESSED_INPUT;     break;
114                case 'p':
115                    newinmode |= ENABLE_PROCESSED_INPUT;     break;
116                case 'n':
117                    notoutmode |= ENABLE_WRAP_AT_EOL_OUTPUT; break;
118                case 'w':
119                    newoutmode |= ENABLE_WRAP_AT_EOL_OUTPUT; break;
120                case 'r':
121                    notoutmode |= ENABLE_PROCESSED_OUTPUT;   break;
122                case 'f':
123                    newoutmode |= ENABLE_PROCESSED_OUTPUT;   break;
124                case 'o':
125                    if (*(argv + 1) && *(argv + 1)[0] != '-') {
126                        *(++argv);
127                        timeout = atoi(*argv) / 1000;
128                        --argc;
129                    }
130                    else {
131                        timeout = 0;
132                    }
133                    break;
134                case 'v':
135                    verbose = TRUE;
136                    break;
137                case 't':
138                    contitle = *(++argv);
139                    --argc;
140                    break;
141                case '?':
142                    printf(options, arg0);
143                    exit(1);
144                default:
145                    printf("wintty option %s not recognized, use -? for help.\n\n", *argv);
146                    exit(1);
147            }
148        }
149        else {
150            printf("wintty argument %s not understood, use -? for help.\n\n", *argv);
151            exit(1);
152        }
153    }
154
155    hproc = GetCurrentProcess();
156    hsavewinsta = GetProcessWindowStation();
157    if (!hsavewinsta || hsavewinsta == INVALID_HANDLE_VALUE) {
158        printerr("GetProcessWindowStation() failed (%d)\n", GetLastError());
159    }
160    else if (!GetUserObjectInformation(hsavewinsta, UOI_NAME, str, sizeof(str), &len)) {
161        printerr("GetUserObjectInfoformation(hWinSta) failed (%d)\n", GetLastError());
162    }
163    else if (strnicmp(str, "Service-", 8) == 0) {
164        printerr("WindowStation Name %s is a service\n", str);
165        isservice = TRUE;
166    }
167    SetLastError(0);
168
169    hstdin = GetStdHandle(STD_INPUT_HANDLE);
170    if (!hstdin || hstdin == INVALID_HANDLE_VALUE) {
171        printerr("GetStdHandle(STD_INPUT_HANDLE) failed (%d)\n",
172                 GetLastError());
173    }
174    else if (DuplicateHandle(hproc, hstdin, hproc, &hdup, 0,
175                             isservice, DUPLICATE_SAME_ACCESS)) {
176        CloseHandle(hstdin);
177        hstdin = hdup;
178    }
179    else {
180        printerr("DupHandle(stdin [%x]) failed (%d)\n",
181                 hstdin, GetLastError());
182    }
183
184    hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
185    if (!hstdout || hstdout == INVALID_HANDLE_VALUE) {
186        printerr("GetStdHandle(STD_OUTPUT_HANDLE) failed (%d)\n",
187                 GetLastError());
188    }
189    else if (DuplicateHandle(hproc, hstdout, hproc, &hdup, 0,
190                             isservice, DUPLICATE_SAME_ACCESS)) {
191        CloseHandle(hstdout);
192        hstdout = hdup;
193    }
194    else {
195        printerr("DupHandle(stdout [%x]) failed (%d)\n",
196                 hstdout, GetLastError());
197    }
198
199    hstderr = GetStdHandle(STD_ERROR_HANDLE);
200    if (!hstderr || hstderr == INVALID_HANDLE_VALUE) {
201        printerr("GetStdHandle(STD_ERROR_HANDLE) failed (%d)\n",
202                 GetLastError());
203    }
204    else if (DuplicateHandle(hproc, hstderr, hproc, &hdup, 0,
205                             isservice, DUPLICATE_SAME_ACCESS)) {
206        CloseHandle(hstderr);
207        hstderr = hdup;
208    }
209    else {
210        printerr("DupHandle(stderr [%x]) failed (%d)\n",
211                 hstderr, GetLastError());
212    }
213
214    /* You can't close the console till all the handles above were
215     * rescued by DuplicateHandle()
216     */
217    if (!FreeConsole())
218        printerr("FreeConsole() failed (%d)\n", GetLastError());
219
220    if (isservice) {
221#ifdef WE_EVER_FIGURE_OUT_WHY_THIS_DOESNT_WORK
222        hsavedesk = GetThreadDesktop(GetCurrentThreadId());
223        if (!hsavedesk || hsavedesk == INVALID_HANDLE_VALUE) {
224            printerr("GetThreadDesktop(GetTID()) failed (%d)\n", GetLastError());
225        }
226        CloseWindowStation(hwinsta);
227        hwinsta = OpenWindowStation("WinSta0", TRUE, MAXIMUM_ALLOWED);
228        if (!hwinsta || hwinsta == INVALID_HANDLE_VALUE) {
229            printerr("OpenWinSta(WinSta0) failed (%d)\n", GetLastError());
230        }
231        else if (!SetProcessWindowStation(hwinsta)) {
232            printerr("SetProcWinSta(WinSta0) failed (%d)\n", GetLastError());
233        }
234        hdesk = OpenDesktop("Default", 0, TRUE, MAXIMUM_ALLOWED);
235        if (!hdesk || hdesk == INVALID_HANDLE_VALUE) {
236            printerr("OpenDesktop(Default) failed (%d)\n", GetLastError());
237        }
238        else if (!SetThreadDesktop(hdesk)) {
239            printerr("SetThreadDesktop(Default) failed (%d)\n", GetLastError());
240        }
241#else
242        PROCESS_INFORMATION pi;
243        STARTUPINFO si;
244        DWORD exitcode = 1;
245        char appbuff[MAX_PATH];
246        char *appname = NULL;
247        char *cmdline = GetCommandLine();
248
249        if (!GetModuleFileName(NULL, appbuff, sizeof(appbuff))) {
250            appname = appbuff;
251        }
252
253        memset(&si, 0, sizeof(si));
254        si.cb = sizeof(si);
255        si.dwFlags     = STARTF_USESHOWWINDOW
256                       | STARTF_USESTDHANDLES;
257        si.lpDesktop   = "WinSta0\\Default";
258        si.wShowWindow = 1;  /* SW_SHOWNORMAL */
259        si.hStdInput   = hstdin;
260        si.hStdOutput  = hstdout;
261        si.hStdError   = hstderr;
262
263        /* Instantly, upon creating the new process, we will close our
264         * copies of the handles so our parent isn't confused when the
265         * child closes their copy of the handle.  Without this action,
266         * we would hold a copy of the handle, and the parent would not
267         * receive their EOF notification.
268         */
269        if (CreateProcess(appname, cmdline, NULL, NULL, TRUE,
270                          CREATE_SUSPENDED | CREATE_NEW_CONSOLE,
271                          NULL, NULL, &si, &pi)) {
272            CloseHandle(si.hStdInput);
273            CloseHandle(si.hStdOutput);
274            CloseHandle(si.hStdError);
275            ResumeThread(pi.hThread);
276            CloseHandle(pi.hThread);
277            WaitForSingleObject(pi.hProcess, INFINITE);
278            GetExitCodeProcess(pi.hProcess, &exitcode);
279            CloseHandle(pi.hProcess);
280            return exitcode;
281        }
282        return 1;
283#endif
284    }
285
286    if (!AllocConsole()) {
287        printerr("AllocConsole(Default) failed (%d)\n", GetLastError());
288    }
289
290    if (contitle && !SetConsoleTitle(contitle)) {
291        printerr("SetConsoleTitle() failed (%d)\n", GetLastError());
292    }
293
294    conout = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE,
295                        FILE_SHARE_READ | FILE_SHARE_WRITE,
296                        FALSE, OPEN_EXISTING, 0, NULL);
297    if (!conout || conout == INVALID_HANDLE_VALUE) {
298        printerr("CreateFile(CONOUT$) failed (%d)\n", GetLastError());
299    }
300    else if (!GetConsoleMode(conout, &conmode)) {
301        printerr("GetConsoleMode(CONOUT) failed (%d)\n", GetLastError());
302    }
303    else if (!SetConsoleMode(conout, conmode = ((conmode | newoutmode)
304                                                         & ~notoutmode))) {
305        printerr("SetConsoleMode(CONOUT, 0x%x) failed (%d)\n",
306                 conmode, GetLastError());
307    }
308
309    conin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE,
310                       FILE_SHARE_READ | FILE_SHARE_WRITE,
311                       FALSE, OPEN_EXISTING, 0, NULL);
312    if (!conin || conin == INVALID_HANDLE_VALUE) {
313        printerr("CreateFile(CONIN$) failed (%d)\n", GetLastError());
314    }
315    else if (!GetConsoleMode(conin, &conmode)) {
316        printerr("GetConsoleMode(CONIN) failed (%d)\n", GetLastError());
317    }
318    else if (!SetConsoleMode(conin, conmode = ((conmode | newinmode)
319                                                        & ~notinmode))) {
320        printerr("SetConsoleMode(CONIN, 0x%x) failed (%d)\n",
321                 conmode, GetLastError());
322    }
323
324    feed.in = conin;
325    feed.out = hstdout;
326    thread = CreateThread(NULL, 0, feedback, (LPVOID)&feed, 0, &tid);
327
328    while (ReadFile(hstdin, str, sizeof(str), &len, NULL))
329        if (!len || !WriteFile(conout, str, len, &len, NULL))
330           break;
331
332    printerr("[EOF] from stdin (%d)\n", GetLastError());
333
334    CloseHandle(stdout);
335    if (!GetConsoleTitle(str, sizeof(str))) {
336        printerr("SetConsoleTitle() failed (%d)\n", GetLastError());
337    }
338    else {
339        strcat(str, " - [Finished]");
340        if (!SetConsoleTitle(str)) {
341            printerr("SetConsoleTitle() failed (%d)\n", GetLastError());
342        }
343    }
344
345    WaitForSingleObject(thread, timeout);
346    FreeConsole();
347    if (isservice) {
348        if (!SetProcessWindowStation(hsavewinsta)) {
349            len = GetLastError();
350        }
351        if (!SetThreadDesktop(hsavedesk)) {
352            len = GetLastError();
353        }
354        CloseDesktop(hdesk);
355        CloseWindowStation(hwinsta);
356    }
357    return 0;
358}
359
360
361DWORD WINAPI feedback(LPVOID arg)
362{
363    feedback_args_t *feed = (feedback_args_t*)arg;
364    char *str[1024];
365    DWORD len;
366
367    while (ReadFile(feed->in, str, sizeof(str), &len, NULL))
368        if (!len || !WriteFile(feed->out, str, len, &len, NULL))
369            break;
370
371    printerr("[EOF] from Console (%d)\n", GetLastError());
372
373    return 0;
374}
375