1 /* new.c -- console allocation plugin for vlock,
2  *          the VT locking program for linux
3  *
4  * This program is copyright (C) 2007 Frank Benkstein, and is free
5  * software which is freely distributable under the terms of the
6  * GNU General Public License version 2, included as the file COPYING in this
7  * distribution.  It is NOT public domain software, and any
8  * redistribution not permitted by the GNU General Public License is
9  * expressly forbidden without prior written permission from
10  * the author.
11  *
12  */
13 
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <unistd.h>
18 #include <fcntl.h>
19 #include <sys/ioctl.h>
20 #include <sys/wait.h>
21 #include <sys/types.h>
22 #include <errno.h>
23 
24 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
25 #include <sys/consio.h>
26 #else
27 #include <sys/vt.h>
28 #endif
29 
30 #include "vlock_plugin.h"
31 
32 const char *preceeds[] = { "all", NULL };
33 const char *requires[] = { "all", NULL };
34 
35 /* name of the virtual console device */
36 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
37 #define CONSOLE "/dev/ttyv0"
38 #else
39 #define CONSOLE "/dev/tty0"
40 #endif
41 /* template for the device of a given virtual console */
42 #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
43 #define VTNAME "/dev/ttyv%x"
44 #else
45 #define VTNAME "/dev/tty%d"
46 #endif
47 
48 /* Get the currently active console from the given
49  * console file descriptor.  Returns console number
50  * (starting from 1) on success, -1 on error. */
51 #if defined(__FreeBSD__) || defined (__FreeBSD_kernel__)
get_active_console(int consfd)52 static int get_active_console(int consfd)
53 {
54   int n;
55 
56   if (ioctl(consfd, VT_GETACTIVE, &n) == 0)
57     return n;
58   else
59     return -1;
60 }
61 #else
get_active_console(int consfd)62 static int get_active_console(int consfd)
63 {
64   struct vt_stat vtstate;
65 
66   /* get the virtual console status */
67   if (ioctl(consfd, VT_GETSTATE, &vtstate) == 0)
68     return vtstate.v_active;
69   else
70     return -1;
71 }
72 #endif
73 
74 /* Get the device name for the given console number.
75  * Returns the device name or NULL on error. */
get_console_name(int n)76 static char *get_console_name(int n)
77 {
78   static char name[sizeof VTNAME + 2];
79   ssize_t namelen;
80 
81   if (n <= 0)
82     return NULL;
83 
84   /* format the virtual terminal filename from the number */
85 #if defined(__FreeBSD__) || defined (__FreeBSD_kernel__)
86   namelen = snprintf(name, sizeof name, VTNAME, n - 1);
87 #else
88   namelen = snprintf(name, sizeof name, VTNAME, n);
89 #endif
90 
91   if (namelen > (ssize_t) sizeof name) {
92     fprintf(stderr, "vlock-new: virtual terminal number too large\n");
93     return NULL;
94   } else if (namelen < 0) {
95     fprintf(stderr, "vlock-new: error calculating terminal device name: %s\n", strerror(errno));
96     return NULL;
97   } else {
98     return name;
99   }
100 }
101 
102 /* Change to the given console number using the given console
103  * file descriptor. */
activate_console(int consfd,int vtno)104 static int activate_console(int consfd, int vtno)
105 {
106   int c = ioctl(consfd, VT_ACTIVATE, vtno);
107 
108   return c < 0 ? c : ioctl(consfd, VT_WAITACTIVE, vtno);
109 }
110 
111 struct new_console_context {
112   int consfd;
113   int old_vtno;
114   int new_vtno;
115   int saved_stdin;
116   int saved_stdout;
117   int saved_stderr;
118 };
119 
120 /* Run switch to a new console and redirect stdio there. */
vlock_start(void ** ctx_ptr)121 bool vlock_start(void **ctx_ptr)
122 {
123   struct new_console_context *ctx;
124   int vtfd;
125   char *vtname;
126 
127   /* Allocate the context. */
128   if ((ctx = malloc(sizeof *ctx)) == NULL)
129     return false;
130 
131   /* Try stdin first. */
132   ctx->consfd = dup(STDIN_FILENO);
133 
134   /* Get the number of the currently active console. */
135   ctx->old_vtno = get_active_console(ctx->consfd);
136 
137   if (ctx->old_vtno < 0) {
138     /* stdin is does not a virtual console. */
139     (void) close(ctx->consfd);
140 
141     /* XXX: add optional PAM check here */
142 
143     /* Open the virtual console directly. */
144     if ((ctx->consfd = open(CONSOLE, O_RDWR)) < 0) {
145       perror("vlock-new: cannot open virtual console");
146       goto err;
147     }
148 
149     /* Get the number of the currently active console, again. */
150     ctx->old_vtno = get_active_console(ctx->consfd);
151 
152     if (ctx->old_vtno < 0) {
153       perror("vlock-new: could not get the currently active console");
154       goto err;
155     }
156   }
157 
158   /* Get a free virtual terminal number. */
159   if (ioctl(ctx->consfd, VT_OPENQRY, &ctx->new_vtno) < 0) {
160     perror("vlock-new: could not find a free virtual terminal");
161     goto err;
162   }
163 
164   /* Get the device name for the new virtual console. */
165   vtname = get_console_name(ctx->new_vtno);
166 
167   /* Open the free virtual terminal. */
168   if ((vtfd = open(vtname, O_RDWR)) < 0) {
169     perror("vlock-new: cannot open new console");
170     goto err;
171   }
172 
173   /* Work around stupid X11 bug:  When switching immediately after the command
174    * is entered, the enter button may get stuck. */
175   if (getenv("DISPLAY") != NULL)
176     sleep(1);
177 
178   /* Switch to the new virtual terminal. */
179   if (activate_console(ctx->consfd, ctx->new_vtno) < 0) {
180     perror("vlock-new: could not activate new terminal");
181     goto err;
182   }
183 
184   /* Save the stdio file descriptors. */
185   ctx->saved_stdin = dup(STDIN_FILENO);
186   ctx->saved_stdout = dup(STDOUT_FILENO);
187   ctx->saved_stderr = dup(STDERR_FILENO);
188 
189   /* Redirect stdio to virtual terminal. */
190   (void) dup2(vtfd, STDIN_FILENO);
191   (void) dup2(vtfd, STDOUT_FILENO);
192   (void) dup2(vtfd, STDERR_FILENO);
193 
194   /* Close virtual terminal file descriptor. */
195   (void) close(vtfd);
196 
197   *ctx_ptr = ctx;
198   return true;
199 
200 err:
201   errno = 0;
202   free(ctx);
203   return false;
204 }
205 
206 /* Redirect stdio back und switch to the previous console. */
vlock_end(void ** ctx_ptr)207 bool vlock_end(void **ctx_ptr)
208 {
209   struct new_console_context *ctx = *ctx_ptr;
210 
211   if (ctx == NULL)
212     return true;
213 
214   /* Restore saved stdio file descriptors. */
215   (void) dup2(ctx->saved_stdin, STDIN_FILENO);
216   (void) dup2(ctx->saved_stdout, STDOUT_FILENO);
217   (void) dup2(ctx->saved_stderr, STDERR_FILENO);
218 
219   /* Switch back to previous virtual terminal. */
220   if (activate_console(ctx->consfd, ctx->old_vtno) < 0)
221     perror("vlock-new: could not activate previous console");
222 
223 #if !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__)
224   /* Deallocate virtual terminal. */
225   if (ioctl(ctx->consfd, VT_DISALLOCATE, ctx->new_vtno) < 0)
226     perror("vlock-new: could not disallocate console");
227 #endif
228 
229   (void) close(ctx->consfd);
230   free(ctx);
231 
232   return true;
233 }
234