1 /* Copyright (c) 2008, 2009
2 * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de)
3 * Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de)
4 * Micah Cowan (micah@cowan.name)
5 * Sadrul Habib Chowdhury (sadrul@users.sourceforge.net)
6 * Copyright (c) 1993-2002, 2003, 2005, 2006, 2007
7 * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de)
8 * Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de)
9 * Copyright (c) 1987 Oliver Laumann
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3, or (at your option)
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program (see the file COPYING); if not, see
23 * http://www.gnu.org/licenses/, or contact Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
26 ****************************************************************
30 #include <sys/types.h>
32 #include <sys/ioctl.h>
40 static int WriteMessage
__P((int, struct msg
*));
41 static sigret_t AttacherSigInt
__P(SIGPROTOARG
);
42 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
43 static sigret_t AttacherWinch
__P(SIGPROTOARG
);
46 static sigret_t DoLock
__P(SIGPROTOARG
);
47 static void LockTerminal
__P((void));
48 static sigret_t LockHup
__P(SIGPROTOARG
);
49 static void screen_builtin_lck
__P((void));
52 static sigret_t AttacherChld
__P(SIGPROTOARG
);
54 static sigret_t AttachSigCont
__P(SIGPROTOARG
);
56 extern int real_uid
, real_gid
, eff_uid
, eff_gid
;
57 extern char *SockName
, *SockMatch
, SockPath
[];
58 extern char HostName
[];
59 extern struct passwd
*ppp
;
60 extern char *attach_tty
, *attach_term
, *LoginName
, *preselect
;
61 extern int xflag
, dflag
, rflag
, quietflag
, adaptflag
;
62 extern struct mode attach_Mode
;
63 extern struct NewWindow nwin_options
;
64 extern int MasterPid
, attach_fd
;
68 extern int multiattach
, multi_uid
, own_uid
;
69 extern int tty_mode
, tty_oldmode
;
71 static int multipipe
[2];
76 static int ContinuePlease
;
79 AttachSigCont SIGDEFARG
86 static int QueryResult
;
89 QueryResultSuccess SIGDEFARG
96 QueryResultFail SIGDEFARG
103 * Send message to a screen backend.
104 * returns 1 if we could attach one, or 0 if none.
105 * Understands MSG_ATTACH, MSG_DETACH, MSG_POW_DETACH
106 * MSG_CONT, MSG_WINCH and nothing else!
108 * if type == MSG_ATTACH and sockets are used, attaches
109 * tty filedescriptor.
117 int r
, l
= sizeof(*m
);
120 if (m
->type
== MSG_ATTACH
)
121 return SendAttachMsg(s
, m
, attach_fd
);
126 r
= write(s
, (char *)m
+ (sizeof(*m
) - l
), l
);
127 if (r
== -1 && errno
== EINTR
)
129 if (r
== -1 || r
== 0)
146 debug2("Attach: how=%d, tty=%s\n", how
, attach_tty
);
149 while ((how
== MSG_ATTACH
|| how
== MSG_CONT
) && multiattach
)
154 Panic(errno
, "pipe");
155 if (chmod(attach_tty
, 0666))
156 Panic(errno
, "chmod %s", attach_tty
);
157 tty_oldmode
= tty_mode
;
158 eff_uid
= -1; /* make UserContext fork */
159 real_uid
= multi_uid
;
160 if ((ret
= UserContext()) <= 0)
166 Panic(errno
, "UserContext");
168 read(multipipe
[0], &dummy
, 1);
169 if (tty_oldmode
>= 0)
171 chmod(attach_tty
, tty_oldmode
);
182 kill(getpid(), SIGTSTP
);
185 if (ret
== SIG_POWER_BYE
)
190 if ((ppid
= getppid()) > 1)
207 # else /* USE_SETEUID */
208 if ((how
== MSG_ATTACH
|| how
== MSG_CONT
) && multiattach
)
210 real_uid
= multi_uid
;
212 #ifdef HAVE_SETRESUID
213 if (setresuid(multi_uid
, own_uid
, multi_uid
))
214 Panic(errno
, "setresuid");
219 if (chmod(attach_tty
, 0666))
220 Panic(errno
, "chmod %s", attach_tty
);
221 tty_oldmode
= tty_mode
;
223 # endif /* USE_SETEUID */
224 #endif /* MULTIUSER */
226 bzero((char *) &m
, sizeof(m
));
228 m
.protocol_revision
= MSG_REVISION
;
229 strncpy(m
.m_tty
, attach_tty
, sizeof(m
.m_tty
) - 1);
230 m
.m_tty
[sizeof(m
.m_tty
) - 1] = 0;
232 if (how
== MSG_WINCH
)
234 if ((lasts
= MakeClientSocket(0)) >= 0)
236 WriteMessage(lasts
, &m
);
244 if ((lasts
= MakeClientSocket(0)) < 0)
246 Panic(0, "Sorry, cannot contact session \"%s\" again.\r\n",
252 n
= FindSocket(&lasts
, (int *)0, (int *)0, SockMatch
);
256 if (rflag
&& (rflag
& 1) == 0)
260 Panic(0, SockMatch
&& *SockMatch
? "There is no screen to be %sed matching %s." : "There is no screen to be %sed.",
272 Panic(0, "Type \"screen [-d] -r [pid.]tty.host\" to resume one of them.");
278 * Go in UserContext. Advantage is, you can kill your attacher
279 * when things go wrong. Any disadvantages? jw.
280 * Do this before the attach to prevent races!
286 #if defined(MULTIUSER) && defined(USE_SETEUID)
289 /* This call to xsetuid should also set the saved uid */
290 xseteuid(real_uid
); /* multi_uid, allow backend to send signals */
297 debug2("Attach: uid %d euid %d\n", (int)getuid(), (int)geteuid());
299 for (s
= SockName
; *s
; s
++)
301 if (*s
> '9' || *s
< '0')
303 MasterPid
= 10 * MasterPid
+ (*s
- '0');
305 debug1("Attach decided, it is '%s'\n", SockPath
);
306 debug1("Attach found MasterPid == %d\n", MasterPid
);
307 if (stat(SockPath
, &st
) == -1)
308 Panic(errno
, "stat %s", SockPath
);
309 if ((st
.st_mode
& 0600) != 0600)
310 Panic(0, "Socket is in wrong mode (%03o)", (int)st
.st_mode
);
313 * Change: if -x or -r ignore failing -d
315 if ((xflag
|| rflag
) && dflag
&& (st
.st_mode
& 0700) == 0600)
319 * Without -x, the mode must match.
320 * With -x the mode is irrelevant unless -d.
322 if ((dflag
|| !xflag
) && (st
.st_mode
& 0700) != (dflag
? 0700 : 0600))
323 Panic(0, "That screen is %sdetached.", dflag
? "already " : "not ");
326 (how
== MSG_DETACH
|| how
== MSG_POW_DETACH
))
328 m
.m
.detach
.dpid
= getpid();
329 strncpy(m
.m
.detach
.duser
, LoginName
, sizeof(m
.m
.detach
.duser
) - 1);
330 m
.m
.detach
.duser
[sizeof(m
.m
.detach
.duser
) - 1] = 0;
333 m
.type
= MSG_POW_DETACH
;
337 /* If there is no password for the session, or the user enters the correct
338 * password, then we get a SIGCONT. Otherwise we get a SIG_BYE */
339 signal(SIGCONT
, AttachSigCont
);
340 if (WriteMessage(lasts
, &m
))
341 Panic(errno
, "WriteMessage");
343 while (!ContinuePlease
)
344 pause(); /* wait for SIGCONT */
345 signal(SIGCONT
, SIG_DFL
);
347 if (how
!= MSG_ATTACH
)
348 return 0; /* we detached it. jw. */
349 sleep(1); /* we dont want to overrun our poor backend. jw. */
350 if ((lasts
= MakeClientSocket(0)) == -1)
351 Panic(0, "Cannot contact screen again. Sigh.");
355 ASSERT(how
== MSG_ATTACH
|| how
== MSG_CONT
);
356 strncpy(m
.m
.attach
.envterm
, attach_term
, sizeof(m
.m
.attach
.envterm
) - 1);
357 m
.m
.attach
.envterm
[sizeof(m
.m
.attach
.envterm
) - 1] = 0;
358 debug1("attach: sending %d bytes... ", (int)sizeof(m
));
360 strncpy(m
.m
.attach
.auser
, LoginName
, sizeof(m
.m
.attach
.auser
) - 1);
361 m
.m
.attach
.auser
[sizeof(m
.m
.attach
.auser
) - 1] = 0;
362 m
.m
.attach
.esc
= DefaultEsc
;
363 m
.m
.attach
.meta_esc
= DefaultMetaEsc
;
364 strncpy(m
.m
.attach
.preselect
, preselect
? preselect
: "", sizeof(m
.m
.attach
.preselect
) - 1);
365 m
.m
.attach
.preselect
[sizeof(m
.m
.attach
.preselect
) - 1] = 0;
366 m
.m
.attach
.apid
= getpid();
367 m
.m
.attach
.adaptflag
= adaptflag
;
368 m
.m
.attach
.lines
= m
.m
.attach
.columns
= 0;
369 if ((s
= getenv("LINES")))
370 m
.m
.attach
.lines
= atoi(s
);
371 if ((s
= getenv("COLUMNS")))
372 m
.m
.attach
.columns
= atoi(s
);
373 m
.m
.attach
.encoding
= nwin_options
.encoding
> 0 ? nwin_options
.encoding
+ 1 : 0;
378 m
.m
.attach
.detachfirst
= MSG_POW_DETACH
;
382 m
.m
.attach
.detachfirst
= MSG_DETACH
;
385 m
.m
.attach
.detachfirst
= MSG_ATTACH
;
388 /* setup CONT signal handler to repair the terminal mode */
389 if (multi
&& (how
== MSG_ATTACH
|| how
== MSG_CONT
))
390 signal(SIGCONT
, AttachSigCont
);
393 if (WriteMessage(lasts
, &m
))
394 Panic(errno
, "WriteMessage");
396 debug1("Attach(%d): sent\n", m
.type
);
398 if (multi
&& (how
== MSG_ATTACH
|| how
== MSG_CONT
))
400 while (!ContinuePlease
)
401 pause(); /* wait for SIGCONT */
402 signal(SIGCONT
, SIG_DFL
);
408 if (tty_oldmode
>= 0)
409 if (chmod(attach_tty
, tty_oldmode
))
410 Panic(errno
, "chmod %s", attach_tty
);
421 static int AttacherPanic
= 0;
425 AttacherChld SIGDEFARG
433 AttacherSigAlarm SIGDEFARG
436 static int tick_cnt
= 0;
437 if ((tick_cnt
= (tick_cnt
+ 1) % 4) == 0)
444 * the frontend's Interrupt handler
445 * we forward SIGINT to the poor backend
448 AttacherSigInt SIGDEFARG
450 signal(SIGINT
, AttacherSigInt
);
451 Kill(MasterPid
, SIGINT
);
456 * Unfortunatelly this is also the SIGHUP handler, so we have to
457 * check if the backend is already detached.
461 AttacherFinit SIGDEFARG
467 debug("AttacherFinit();\n");
468 signal(SIGHUP
, SIG_IGN
);
469 /* Check if signal comes from backend */
470 if (stat(SockPath
, &statb
) == 0 && (statb
.st_mode
& 0777) != 0600)
472 debug("Detaching backend!\n");
473 bzero((char *) &m
, sizeof(m
));
474 strncpy(m
.m_tty
, attach_tty
, sizeof(m
.m_tty
) - 1);
475 m
.m_tty
[sizeof(m
.m_tty
) - 1] = 0;
476 debug1("attach_tty is %s\n", attach_tty
);
477 m
.m
.detach
.dpid
= getpid();
479 m
.protocol_revision
= MSG_REVISION
;
480 if ((s
= MakeClientSocket(0)) >= 0)
487 if (tty_oldmode
>= 0)
490 chmod(attach_tty
, tty_oldmode
);
499 AttacherFinitBye SIGDEFARG
502 debug("AttacherFintBye()\n");
503 #if defined(MULTIUSER) && !defined(USE_SETEUID)
513 /* we don't want to disturb init (even if we were root), eh? jw */
514 if ((ppid
= getppid()) > 1)
515 Kill(ppid
, SIGHUP
); /* carefully say good bye. jw. */
521 #if defined(DEBUG) && defined(SIG_NODEBUG)
523 AttacherNoDebug SIGDEFARG
525 debug("AttacherNoDebug()\n");
526 signal(SIG_NODEBUG
, AttacherNoDebug
);
529 debug("debug: closing debug file.\n");
536 #endif /* SIG_NODEBUG */
538 static int SuspendPlease
;
543 debug("SigStop()\n");
549 static int LockPlease
;
555 signal(SIG_LOCK
, DoLock
);
563 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
564 static int SigWinchPlease
;
567 AttacherWinch SIGDEFARG
569 debug("AttacherWinch()\n");
577 * Attacher loop - no return
583 signal(SIGHUP
, AttacherFinit
);
584 signal(SIG_BYE
, AttacherFinit
);
586 signal(SIG_POWER_BYE
, AttacherFinitBye
);
588 #if defined(DEBUG) && defined(SIG_NODEBUG)
589 signal(SIG_NODEBUG
, AttacherNoDebug
);
592 signal(SIG_LOCK
, DoLock
);
594 signal(SIGINT
, AttacherSigInt
);
596 signal(SIG_STOP
, SigStop
);
598 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
599 signal(SIGWINCH
, AttacherWinch
);
602 signal(SIGCHLD
, AttacherChld
);
604 debug("attacher: going for a nap.\n");
611 signal(SIGALRM
, AttacherSigAlarm
);
615 if (kill(MasterPid
, 0) < 0 && errno
!= EPERM
)
617 debug1("attacher: Panic! MasterPid %d does not exist.\n", MasterPid
);
622 fcntl(0, F_SETFL
, 0);
623 SetTTY(0, &attach_Mode
);
624 printf("\nSuddenly the Dungeon collapses!! - You die...\n");
631 #if defined(MULTIUSER) && !defined(USE_SETEUID)
635 signal(SIGTSTP
, SIG_DFL
);
636 debug("attacher: killing myself SIGTSTP\n");
637 kill(getpid(), SIGTSTP
);
638 debug("attacher: continuing from stop\n");
639 signal(SIG_STOP
, SigStop
);
640 (void) Attach(MSG_CONT
);
647 #if defined(MULTIUSER) && !defined(USE_SETEUID)
653 signal(SIG_LOCK
, DoLock
);
655 (void) Attach(MSG_CONT
);
658 #if defined(SIGWINCH) && defined(TIOCGWINSZ)
663 signal(SIGWINCH
, AttacherWinch
);
665 (void) Attach(MSG_WINCH
);
667 #endif /* SIGWINCH */
673 /* ADDED by Rainer Pruy 10/15/87 */
674 /* POLISHED by mls. 03/10/91 */
676 static char LockEnd
[] = "Welcome back to screen !!\n";
681 int ppid
= getppid();
698 sigret_t (*sigs
[NSIG
])__P(SIGPROTOARG
);
700 for (sig
= 1; sig
< NSIG
; sig
++)
701 sigs
[sig
] = signal(sig
, sig
== SIGCHLD
? SIG_DFL
: SIG_IGN
);
702 signal(SIGHUP
, LockHup
);
705 prg
= getenv("LOCKPRG");
706 if (prg
&& strcmp(prg
, "builtin") && !access(prg
, X_OK
))
708 signal(SIGCHLD
, SIG_DFL
);
709 debug1("lockterminal: '%s' seems executable, execl it!\n", prg
);
710 if ((pid
= fork()) == 0)
717 setuid(real_uid
); /* this should be done already */
719 closeallfiles(0); /* important: /etc/shadow may be open */
720 execl(prg
, "SCREEN-LOCK", NULL
);
724 Msg(errno
, "Cannot lock terminal - fork failed");
735 signal(SIGCHLD
, SIG_DFL
);
738 while (((wret
= wait(&wstat
)) != pid
) ||
739 ((wret
== -1) && (errno
== EINTR
))
748 else if (WTERMSIG(wstat
) != 0)
750 fprintf(stderr
, "Lock: %s: Killed by signal: %d%s\n", prg
,
751 WTERMSIG(wstat
), WIFCORESIG(wstat
) ? " (Core dumped)" : "");
754 else if (WEXITSTATUS(wstat
))
756 debug2("Lock: %s: return code %d\n", prg
, WEXITSTATUS(wstat
));
766 debug1("lockterminal: '%s' seems NOT executable, we use our builtin\n", prg
);
770 debug("lockterminal: using buitin.\n");
772 screen_builtin_lck();
775 for (sig
= 1; sig
< NSIG
; sig
++)
777 if (sigs
[sig
] != (sigret_t(*)__P(SIGPROTOARG
)) -1)
778 signal(sig
, sigs
[sig
]);
785 * PAM support by Pablo Averbuj <pablo@averbuj.com>
788 #include <security/pam_appl.h>
790 static int PAM_conv
__P((int, const struct pam_message
**, struct pam_response
**, void *));
793 PAM_conv(num_msg
, msg
, resp
, appdata_ptr
)
795 const struct pam_message
**msg
;
796 struct pam_response
**resp
;
800 struct pam_response
*reply
= NULL
;
802 reply
= malloc(sizeof(struct pam_response
)*num_msg
);
805 #define COPY_STRING(s) (s) ? strdup(s) : NULL
807 for (replies
= 0; replies
< num_msg
; replies
++)
809 switch (msg
[replies
]->msg_style
)
811 case PAM_PROMPT_ECHO_OFF
:
813 reply
[replies
].resp_retcode
= PAM_SUCCESS
;
814 reply
[replies
].resp
= appdata_ptr
? strdup((char *)appdata_ptr
) : 0;
817 /* ignore the informational mesage */
818 /* but first clear out any drek left by malloc */
819 reply
[replies
].resp
= NULL
;
821 case PAM_PROMPT_ECHO_ON
:
822 /* user name given to PAM already */
825 /* unknown or PAM_ERROR_MSG */
834 static struct pam_conv PAM_conversation
= {
842 /* -- original copyright by Luigi Cannelloni 1985 (luigi@faui70.UUCP) -- */
846 char fullname
[100], *cp1
, message
[100 + 100];
848 pam_handle_t
*pamh
= 0;
851 char *pass
, mypass
[16 + 1], salt
[3];
855 pass
= ppp
->pw_passwd
;
856 if (pass
== 0 || *pass
== 0)
858 if ((pass
= getpass("Key: ")))
860 strncpy(mypass
, pass
, sizeof(mypass
) - 1);
861 mypass
[sizeof(mypass
) - 1] = 0;
864 if ((pass
= getpass("Again: ")))
866 if (strcmp(mypass
, pass
))
868 fprintf(stderr
, "Passwords don't match.\007\n");
876 fprintf(stderr
, "Getpass error.\007\n");
881 salt
[0] = 'A' + (int)(time(0) % 26);
882 salt
[1] = 'A' + (int)((time(0) >> 6) % 26);
884 pass
= crypt(mypass
, salt
);
885 pass
= ppp
->pw_passwd
= SaveStr(pass
);
889 debug("screen_builtin_lck looking in gcos field\n");
890 strncpy(fullname
, ppp
->pw_gecos
, sizeof(fullname
) - 9);
891 fullname
[sizeof(fullname
) - 9] = 0;
893 if ((cp1
= index(fullname
, ',')) != NULL
)
895 if ((cp1
= index(fullname
, '&')) != NULL
)
897 strncpy(cp1
, ppp
->pw_name
, 8);
899 if (*cp1
>= 'a' && *cp1
<= 'z')
903 sprintf(message
, "Screen used by %s%s<%s> on %s.\nPassword:\007",
904 fullname
, fullname
[0] ? " " : "", ppp
->pw_name
, HostName
);
906 /* loop here to wait for correct password */
909 debug("screen_builtin_lck awaiting password\n");
911 if ((cp1
= getpass(message
)) == NULL
)
913 AttacherFinit(SIGARG
);
917 PAM_conversation
.appdata_ptr
= cp1
;
918 pam_error
= pam_start("screen", ppp
->pw_name
, &PAM_conversation
, &pamh
);
919 if (pam_error
!= PAM_SUCCESS
)
920 AttacherFinit(SIGARG
); /* goodbye */
921 pam_error
= pam_authenticate(pamh
, 0);
922 pam_end(pamh
, pam_error
);
923 PAM_conversation
.appdata_ptr
= 0;
924 if (pam_error
== PAM_SUCCESS
)
927 if (!strncmp(crypt(cp1
, pass
), pass
, strlen(pass
)))
930 debug("screen_builtin_lck: NO!!!!!\n");
931 bzero(cp1
, strlen(cp1
));
933 bzero(cp1
, strlen(cp1
));
934 debug("password ok.\n");
941 SendCmdMessage(sty
, match
, av
, query
)
954 i
= FindSocket(&s
, (int *)0, (int *)0, match
);
956 Panic(0, "No screen session found.");
958 Panic(0, "Use -S to specify a session.");
963 if (strlen(sty
) > NAME_MAX
)
966 if (strlen(sty
) > 2 * MAXSTR
- 1)
967 sty
[2 * MAXSTR
- 1] = 0;
968 sprintf(SockPath
+ strlen(SockPath
), "/%s", sty
);
969 if ((s
= MakeClientSocket(1)) == -1)
972 bzero((char *)&m
, sizeof(m
));
973 m
.type
= query
? MSG_QUERY
: MSG_COMMAND
;
976 strncpy(m
.m_tty
, attach_tty
, sizeof(m
.m_tty
) - 1);
977 m
.m_tty
[sizeof(m
.m_tty
) - 1] = 0;
981 for (; *av
&& n
< MAXARGS
- 1; ++av
, ++n
)
983 len
= strlen(*av
) + 1;
984 if (p
+ len
>= m
.m
.command
.cmd
+ sizeof(m
.m
.command
.cmd
) - 1)
990 m
.m
.command
.nargs
= n
;
991 strncpy(m
.m
.attach
.auser
, LoginName
, sizeof(m
.m
.attach
.auser
) - 1);
992 m
.m
.command
.auser
[sizeof(m
.m
.command
.auser
) - 1] = 0;
993 m
.protocol_revision
= MSG_REVISION
;
994 strncpy(m
.m
.command
.preselect
, preselect
? preselect
: "", sizeof(m
.m
.command
.preselect
) - 1);
995 m
.m
.command
.preselect
[sizeof(m
.m
.command
.preselect
) - 1] = 0;
996 m
.m
.command
.apid
= getpid();
997 debug1("SendCommandMsg writing '%s'\n", m
.m
.command
.cmd
);
1000 /* Create a server socket so we can get back the result */
1001 char *sp
= SockPath
+ strlen(SockPath
);
1002 char query
[] = "-queryX";
1005 for (c
= 'A'; c
<= 'Z'; c
++)
1008 strcpy(sp
, query
); /* XXX: strncpy? */
1009 if ((r
= MakeServerSocket()) >= 0)
1014 for (c
= '0'; c
<= '9'; c
++)
1018 if ((r
= MakeServerSocket()) >= 0)
1024 Panic(0, "Could not create a listening socket to read the results.");
1026 strncpy(m
.m
.command
.writeback
, SockPath
, sizeof(m
.m
.command
.writeback
) - 1);
1027 m
.m
.command
.writeback
[sizeof(m
.m
.command
.writeback
) - 1] = '\0';
1029 /* Send the message, then wait for a response */
1030 signal(SIGCONT
, QueryResultSuccess
);
1031 signal(SIG_BYE
, QueryResultFail
);
1032 if (WriteMessage(s
, &m
))
1033 Msg(errno
, "write");
1035 while (!QueryResult
)
1037 signal(SIGCONT
, SIG_DFL
);
1038 signal(SIG_BYE
, SIG_DFL
);
1040 /* Read the result and spit it out to stdout */
1043 if (QueryResult
== 2) /* An error happened */
1048 if (WriteMessage(s
, &m
))
1049 Msg(errno
, "write");