add in sample.conf to defaults_DATA; needed by vuseradd
[util-vserver.git] / src / vlogin.c
1 // $Id: vlogin.c 2325 2006-09-21 19:42:31Z dhozac $
2
3 // Copyright (C) 2006 Benedikt Böhm <hollow@gentoo.org>
4 // Based on vserver-utils' vlogin program.
5 //  
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; version 2 of the License.
9 //  
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //  
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19
20 #ifdef HAVE_CONFIG_H
21 #  include <config.h>
22 #endif
23
24 #include "util.h"
25 #include <lib/vserver.h>
26 #include <lib/fmt.h>
27
28 #include <stdlib.h>
29 #include <getopt.h>
30 #include <stdint.h>
31 #include <string.h>
32 #include <errno.h>
33 #include <sys/stat.h>
34 #include <sys/ioctl.h>
35 #include <sys/wait.h>
36 #include <sys/socket.h>
37 #include <termios.h>
38 #include <signal.h>
39 #include <pty.h>
40 #include <fcntl.h>
41
42 #define ENSC_WRAPPERS_PREFIX    "vlogin: "
43 #define ENSC_WRAPPERS_IOCTL     1
44 #define ENSC_WRAPPERS_UNISTD    1
45 #define ENSC_WRAPPERS_SOCKET    1
46 #define ENSC_WRAPPERS_IO        1
47 #define ENSC_WRAPPERS_TERMIOS   1
48 #define ENSC_WRAPPERS_FCNTL     1
49 #include <wrappers.h>
50
51 struct terminal {
52   int fd;                           /* terminal file descriptor */
53   struct termios term;              /* terminal settings */
54   struct winsize ws;                /* terminal size */
55   pid_t pid;                        /* terminal process id */
56   struct termios termo;             /* original terminal settings */
57   enum { TS_RESET, TS_RAW } state;  /* terminal state */
58 };
59
60 static struct terminal t;
61 extern int wrapper_exit_code;
62
63 /* set terminal to raw mode */
64 static void
65 terminal_raw(void)
66 {
67   struct termios buf;
68
69   /* save original terminal settings */
70   Etcgetattr(STDIN_FILENO, &t.termo);
71
72   buf = t.termo;
73   
74   /* convert terminal settings to raw mode */
75   cfmakeraw(&buf);
76
77   /* apply raw terminal settings */
78   Etcsetattr(STDIN_FILENO, TCSAFLUSH, &buf);
79
80   t.state = TS_RAW;
81 }
82
83 /* reset terminal to original state */
84 static void
85 terminal_reset(void)
86 {
87   if (t.state != TS_RAW)
88     return;
89
90   Etcsetattr(STDIN_FILENO, TCSAFLUSH, &t.termo);
91
92   t.state = TS_RESET;
93 }
94
95 /* send signal to terminal */
96 static void
97 terminal_kill(int sig)
98 {
99   pid_t pgrp = -1;
100
101   /* try to get process group leader */
102   if (ioctl(t.fd, TIOCGPGRP, &pgrp) >= 0 &&
103       pgrp != -1 &&
104       kill(-pgrp, sig) != -1)
105     return;
106
107   /* fallback using terminal pid */
108   kill(-t.pid, sig);
109 }
110
111 /* redraw the terminal screen */
112 static void
113 terminal_redraw(void)
114 {
115   /* get winsize from stdin */
116   if (ioctl(STDIN_FILENO, TIOCGWINSZ, &t.ws) == -1)
117     return;
118
119   /* set winsize in terminal */
120   ioctl(t.fd, TIOCSWINSZ, &t.ws);
121
122   /* set winsize change signal to terminal */
123   terminal_kill(SIGWINCH);
124 }
125
126 /* copy terminal activities */
127 static void
128 terminal_copy(int src, int dst)
129 {
130   char buf[64];
131   ssize_t len;
132
133   /* read terminal activity */
134   len = read(src, buf, sizeof(buf));
135   if (len == -1 && errno != EINTR) {
136     perror("read()");
137     terminal_kill(SIGTERM);
138     exit(1);
139   } else if (len == -1)
140     return;
141
142   /* write activity to user */
143   EwriteAll(dst, buf, len);
144 }
145
146 /* shuffle all output, and reset the terminal */
147 static void
148 terminal_end(void)
149 {
150   char buf[64];
151   ssize_t len;
152   long options;
153
154   options = Efcntl(t.fd, F_GETFL, 0) | O_NONBLOCK;
155   Efcntl(t.fd, F_SETFL, options);
156   for (;;) {
157     len = read(t.fd, buf, sizeof(buf));
158     if (len == 0 || len == -1)
159       break;
160     EwriteAll(STDOUT_FILENO, buf, len);
161   }
162
163   /* in case atexit hasn't been setup yet */
164   terminal_reset();
165 }
166
167 /* catch signals */
168 static void
169 signal_handler(int sig)
170 {
171   int status;
172
173   switch(sig) {
174     /* catch interrupt */
175     case SIGINT:
176       terminal_kill(sig);
177       break;
178
179     /* terminal died */
180     case SIGCHLD:
181       terminal_end();
182       wait(&status);
183       exit(WEXITSTATUS(status));
184       break;
185
186     /* window size has changed */
187     case SIGWINCH:
188       terminal_redraw();
189       break;
190
191     default:
192       exit(0);
193   }
194
195 }
196
197 void do_vlogin(int argc, char *argv[], int ind)
198 {
199   int slave;
200   pid_t pid;
201   int n, i;
202   fd_set rfds;
203
204   if (!isatty(0) || !isatty(1)) {
205     execvp(argv[ind], argv+ind);
206     return;
207   }
208
209   /* set terminal to raw mode */
210   terminal_raw();
211
212   /* reset terminal to its original mode */
213   atexit(terminal_reset);
214
215   /* fork new pseudo terminal */
216   if (openpty(&t.fd, &slave, NULL, NULL, NULL) == -1) {
217     perror(ENSC_WRAPPERS_PREFIX "openpty()");
218     exit(EXIT_FAILURE);
219   }
220
221   /* setup SIGCHLD here, so we're sure to get the signal */
222   signal(SIGCHLD, signal_handler);
223
224   pid = Efork();
225
226   if (pid == 0) {
227     /* we don't need the master side of the terminal */
228     close(t.fd);
229
230     /* login_tty() stupid dietlibc doesn't have it */
231     Esetsid();
232
233     Eioctl(slave, TIOCSCTTY, NULL);
234
235     Edup2(slave, 0);
236     Edup2(slave, 1);
237     Edup2(slave, 2);
238
239     if (slave > 2)
240       close(slave);
241
242     Eexecvp(argv[ind], argv+ind);
243   }
244
245   /* setup SIGINT and SIGWINCH here, as they can cause loops in the child */
246   signal(SIGWINCH, signal_handler);
247   signal(SIGINT, signal_handler);
248
249   /* save terminals pid */
250   t.pid = pid;
251
252   /* set process title for ps */
253   n = strlen(argv[0]);
254
255   for (i = 0; i < argc; i++)
256     memset(argv[i], '\0', strlen(argv[i]));
257
258   strncpy(argv[0], "login", n);
259
260   /* we want a redraw */
261   terminal_redraw();
262
263   /* main loop */
264   for (;;) {
265     /* init file descriptors for select */
266     FD_ZERO(&rfds);
267     FD_SET(STDIN_FILENO, &rfds);
268     FD_SET(t.fd, &rfds);
269     n = t.fd;
270
271     /* wait for something to happen */
272     while (select(n + 1, &rfds, NULL, NULL, NULL) == -1) {
273       if (errno == EINTR || errno == EAGAIN)
274         continue;
275       perror(ENSC_WRAPPERS_PREFIX "select()");
276       exit(wrapper_exit_code);
277     }
278
279     if (FD_ISSET(STDIN_FILENO, &rfds))
280       terminal_copy(STDIN_FILENO, t.fd);
281
282     if (FD_ISSET(t.fd, &rfds))
283       terminal_copy(t.fd, STDOUT_FILENO);
284   }
285
286   /* never get here, signal handler exits */
287 }