2872 lines
69 KiB
C
2872 lines
69 KiB
C
/*
|
|
* Alternate Getty (agetty) 'agetty' is a versatile, portable, easy to use
|
|
* replacement for getty on SunOS 4.1.x or the SAC ttymon/ttyadm/sacadm/pmadm
|
|
* suite on Solaris and other SVR4 systems. 'agetty' was written by Wietse
|
|
* Venema, enhanced by John DiMarco, and further enhanced by Dennis Cronin.
|
|
*
|
|
* Ported to Linux by Peter Orbaek <poe@daimi.aau.dk>
|
|
* Adopt the mingetty features for a better support
|
|
* of virtual consoles by Werner Fink <werner@suse.de>
|
|
*
|
|
* This program is freely distributable.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <termios.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <fcntl.h>
|
|
#include <stdarg.h>
|
|
#include <ctype.h>
|
|
#include <utmpx.h>
|
|
#include <getopt.h>
|
|
#include <time.h>
|
|
#include <sys/socket.h>
|
|
#include <langinfo.h>
|
|
#include <grp.h>
|
|
#include <arpa/inet.h>
|
|
#include <netdb.h>
|
|
#include <ifaddrs.h>
|
|
#include <net/if.h>
|
|
#include <sys/utsname.h>
|
|
|
|
#include "strutils.h"
|
|
#include "all-io.h"
|
|
#include "nls.h"
|
|
#include "pathnames.h"
|
|
#include "c.h"
|
|
#include "cctype.h"
|
|
#include "widechar.h"
|
|
#include "ttyutils.h"
|
|
#include "color-names.h"
|
|
#include "env.h"
|
|
|
|
#ifdef USE_PLYMOUTH_SUPPORT
|
|
# include "plymouth-ctrl.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_SYS_PARAM_H
|
|
# include <sys/param.h>
|
|
#endif
|
|
|
|
#if defined(__FreeBSD_kernel__)
|
|
# include <pty.h>
|
|
# ifdef HAVE_UTMP_H
|
|
# include <utmp.h>
|
|
# endif
|
|
# ifdef HAVE_LIBUTIL_H
|
|
# include <libutil.h>
|
|
# endif
|
|
#endif
|
|
|
|
#ifdef __linux__
|
|
# include <sys/kd.h>
|
|
# define USE_SYSLOG
|
|
# ifndef DEFAULT_VCTERM
|
|
# define DEFAULT_VCTERM "linux"
|
|
# endif
|
|
# if defined (__s390__) || defined (__s390x__)
|
|
# define DEFAULT_TTYS0 "dumb"
|
|
# define DEFAULT_TTY32 "ibm327x"
|
|
# define DEFAULT_TTYS1 "vt220"
|
|
# endif
|
|
# ifndef DEFAULT_STERM
|
|
# define DEFAULT_STERM "vt102"
|
|
# endif
|
|
#elif defined(__GNU__)
|
|
# define USE_SYSLOG
|
|
# ifndef DEFAULT_VCTERM
|
|
# define DEFAULT_VCTERM "hurd"
|
|
# endif
|
|
# ifndef DEFAULT_STERM
|
|
# define DEFAULT_STERM "vt102"
|
|
# endif
|
|
#else
|
|
# ifndef DEFAULT_VCTERM
|
|
# define DEFAULT_VCTERM "vt100"
|
|
# endif
|
|
# ifndef DEFAULT_STERM
|
|
# define DEFAULT_STERM "vt100"
|
|
# endif
|
|
#endif
|
|
|
|
#ifdef __FreeBSD_kernel__
|
|
#define USE_SYSLOG
|
|
#endif
|
|
|
|
/* If USE_SYSLOG is undefined all diagnostics go to /dev/console. */
|
|
#ifdef USE_SYSLOG
|
|
# include <syslog.h>
|
|
#endif
|
|
|
|
/*
|
|
* Some heuristics to find out what environment we are in: if it is not
|
|
* System V, assume it is SunOS 4. The LOGIN_PROCESS is defined in System V
|
|
* utmp.h, which will select System V style getty.
|
|
*/
|
|
#ifdef LOGIN_PROCESS
|
|
# define SYSV_STYLE
|
|
#endif
|
|
|
|
/*
|
|
* Things you may want to modify.
|
|
*
|
|
* If ISSUE_SUPPORT is not defined, agetty will never display the contents of
|
|
* the /etc/issue file. You will not want to spit out large "issue" files at
|
|
* the wrong baud rate. Relevant for System V only.
|
|
*
|
|
* You may disagree with the default line-editing etc. characters defined
|
|
* below. Note, however, that DEL cannot be used for interrupt generation
|
|
* and for line editing at the same time.
|
|
*/
|
|
|
|
/* Displayed before the login prompt. */
|
|
#ifdef SYSV_STYLE
|
|
# define ISSUE_SUPPORT
|
|
# if defined(HAVE_SCANDIRAT) && defined(HAVE_OPENAT)
|
|
# include <dirent.h>
|
|
# include "fileutils.h"
|
|
# define ISSUEDIR_SUPPORT
|
|
# define ISSUEDIR_EXT ".issue"
|
|
# define ISSUEDIR_EXTSIZ (sizeof(ISSUEDIR_EXT) - 1)
|
|
# endif
|
|
#endif
|
|
|
|
/* Login prompt. */
|
|
#define LOGIN "login: "
|
|
#define LOGIN_ARGV_MAX 16 /* Numbers of args for login */
|
|
|
|
/*
|
|
* agetty --reload
|
|
*/
|
|
#ifdef AGETTY_RELOAD
|
|
# include <sys/inotify.h>
|
|
# include <linux/netlink.h>
|
|
# include <linux/rtnetlink.h>
|
|
# define AGETTY_RELOAD_FILENAME "/run/agetty.reload" /* trigger file */
|
|
# define AGETTY_RELOAD_FDNONE -2 /* uninitialized fd */
|
|
static int inotify_fd = AGETTY_RELOAD_FDNONE;
|
|
static int netlink_fd = AGETTY_RELOAD_FDNONE;
|
|
static uint32_t netlink_groups;
|
|
#endif
|
|
|
|
struct issue {
|
|
FILE *output;
|
|
char *mem;
|
|
size_t mem_sz;
|
|
|
|
#ifdef AGETTY_RELOAD
|
|
char *mem_old;
|
|
#endif
|
|
unsigned int do_tcsetattr : 1,
|
|
do_tcrestore : 1;
|
|
};
|
|
|
|
/*
|
|
* When multiple baud rates are specified on the command line, the first one
|
|
* we will try is the first one specified.
|
|
*/
|
|
#define FIRST_SPEED 0
|
|
|
|
/* Storage for command-line options. */
|
|
#define MAX_SPEED 10 /* max. nr. of baud rates */
|
|
|
|
struct options {
|
|
int flags; /* toggle switches, see below */
|
|
unsigned int timeout; /* time-out period */
|
|
char *autolog; /* login the user automatically */
|
|
char *chdir; /* Chdir before the login */
|
|
char *chroot; /* Chroot before the login */
|
|
char *login; /* login program */
|
|
char *logopt; /* options for login program */
|
|
char *tty; /* name of tty */
|
|
char *vcline; /* line of virtual console */
|
|
char *term; /* terminal type */
|
|
char *initstring; /* modem init string */
|
|
char *issue; /* alternative issue file or directory */
|
|
char *erasechars; /* string with erase chars */
|
|
char *killchars; /* string with kill chars */
|
|
char *osrelease; /* /etc/os-release data */
|
|
unsigned int delay; /* Sleep seconds before prompt */
|
|
int nice; /* Run login with this priority */
|
|
int numspeed; /* number of baud rates to try */
|
|
int clocal; /* CLOCAL_MODE_* */
|
|
int kbmode; /* Keyboard mode if virtual console */
|
|
speed_t speeds[MAX_SPEED]; /* baud rates to be tried */
|
|
};
|
|
|
|
enum {
|
|
CLOCAL_MODE_AUTO = 0,
|
|
CLOCAL_MODE_ALWAYS,
|
|
CLOCAL_MODE_NEVER
|
|
};
|
|
|
|
#define F_PARSE (1<<0) /* process modem status messages */
|
|
#define F_ISSUE (1<<1) /* display /etc/issue or /etc/issue.d */
|
|
#define F_RTSCTS (1<<2) /* enable RTS/CTS flow control */
|
|
|
|
#define F_INITSTRING (1<<4) /* initstring is set */
|
|
#define F_WAITCRLF (1<<5) /* wait for CR or LF */
|
|
|
|
#define F_NOPROMPT (1<<7) /* do not ask for login name! */
|
|
#define F_LCUC (1<<8) /* support for *LCUC stty modes */
|
|
#define F_KEEPSPEED (1<<9) /* follow baud rate from kernel */
|
|
#define F_KEEPCFLAGS (1<<10) /* reuse c_cflags setup from kernel */
|
|
#define F_EIGHTBITS (1<<11) /* Assume 8bit-clean tty */
|
|
#define F_VCONSOLE (1<<12) /* This is a virtual console */
|
|
#define F_HANGUP (1<<13) /* Do call vhangup(2) */
|
|
#define F_UTF8 (1<<14) /* We can do UTF8 */
|
|
#define F_LOGINPAUSE (1<<15) /* Wait for any key before dropping login prompt */
|
|
#define F_NOCLEAR (1<<16) /* Do not clear the screen before prompting */
|
|
#define F_NONL (1<<17) /* No newline before issue */
|
|
#define F_NOHOSTNAME (1<<18) /* Do not show the hostname */
|
|
#define F_LONGHNAME (1<<19) /* Show Full qualified hostname */
|
|
#define F_NOHINTS (1<<20) /* Don't print hints */
|
|
#define F_REMOTE (1<<21) /* Add '-h fakehost' to login(1) command line */
|
|
|
|
#define serial_tty_option(opt, flag) \
|
|
(((opt)->flags & (F_VCONSOLE|(flag))) == (flag))
|
|
|
|
struct Speedtab {
|
|
long speed;
|
|
speed_t code;
|
|
};
|
|
|
|
static const struct Speedtab speedtab[] = {
|
|
{50, B50},
|
|
{75, B75},
|
|
{110, B110},
|
|
{134, B134},
|
|
{150, B150},
|
|
{200, B200},
|
|
{300, B300},
|
|
{600, B600},
|
|
{1200, B1200},
|
|
{1800, B1800},
|
|
{2400, B2400},
|
|
{4800, B4800},
|
|
{9600, B9600},
|
|
#ifdef B19200
|
|
{19200, B19200},
|
|
#elif defined(EXTA)
|
|
{19200, EXTA},
|
|
#endif
|
|
#ifdef B38400
|
|
{38400, B38400},
|
|
#elif defined(EXTB)
|
|
{38400, EXTB},
|
|
#endif
|
|
#ifdef B57600
|
|
{57600, B57600},
|
|
#endif
|
|
#ifdef B115200
|
|
{115200, B115200},
|
|
#endif
|
|
#ifdef B230400
|
|
{230400, B230400},
|
|
#endif
|
|
#ifdef B460800
|
|
{460800, B460800},
|
|
#endif
|
|
#ifdef B500000
|
|
{500000, B500000},
|
|
#endif
|
|
#ifdef B576000
|
|
{576000, B576000},
|
|
#endif
|
|
#ifdef B921600
|
|
{921600, B921600},
|
|
#endif
|
|
#ifdef B1000000
|
|
{1000000, B1000000},
|
|
#endif
|
|
#ifdef B1152000
|
|
{1152000, B1152000},
|
|
#endif
|
|
#ifdef B1500000
|
|
{1500000, B1500000},
|
|
#endif
|
|
#ifdef B2000000
|
|
{2000000, B2000000},
|
|
#endif
|
|
#ifdef B2500000
|
|
{2500000, B2500000},
|
|
#endif
|
|
#ifdef B3000000
|
|
{3000000, B3000000},
|
|
#endif
|
|
#ifdef B3500000
|
|
{3500000, B3500000},
|
|
#endif
|
|
#ifdef B4000000
|
|
{4000000, B4000000},
|
|
#endif
|
|
{0, 0},
|
|
};
|
|
|
|
static void init_special_char(char* arg, struct options *op);
|
|
static void parse_args(int argc, char **argv, struct options *op);
|
|
static void parse_speeds(struct options *op, char *arg);
|
|
static void update_utmp(struct options *op);
|
|
static void open_tty(char *tty, struct termios *tp, struct options *op);
|
|
static void termio_init(struct options *op, struct termios *tp);
|
|
static void reset_vc(const struct options *op, struct termios *tp, int canon);
|
|
static void auto_baud(struct termios *tp);
|
|
static void list_speeds(void);
|
|
static void output_special_char (struct issue *ie, unsigned char c, struct options *op,
|
|
struct termios *tp, FILE *fp);
|
|
static void do_prompt(struct issue *ie, struct options *op, struct termios *tp);
|
|
static void next_speed(struct options *op, struct termios *tp);
|
|
static char *get_logname(struct issue *ie, struct options *op,
|
|
struct termios *tp, struct chardata *cp);
|
|
static void termio_final(struct options *op,
|
|
struct termios *tp, struct chardata *cp);
|
|
static int caps_lock(char *s);
|
|
static speed_t bcode(char *s);
|
|
static void usage(void) __attribute__((__noreturn__));
|
|
static void exit_slowly(int code) __attribute__((__noreturn__));
|
|
static void log_err(const char *, ...) __attribute__((__noreturn__))
|
|
__attribute__((__format__(printf, 1, 2)));
|
|
static void log_warn (const char *, ...)
|
|
__attribute__((__format__(printf, 1, 2)));
|
|
static ssize_t append(char *dest, size_t len, const char *sep, const char *src);
|
|
static void check_username (const char* nm);
|
|
static void login_options_to_argv(char *argv[], int *argc, char *str, char *username);
|
|
static void reload_agettys(void);
|
|
static void print_issue_file(struct issue *ie, struct options *op, struct termios *tp);
|
|
static void eval_issue_file(struct issue *ie, struct options *op, struct termios *tp);
|
|
|
|
/* Fake hostname for ut_host specified on command line. */
|
|
static char *fakehost;
|
|
|
|
#ifdef DEBUGGING
|
|
# include "closestream.h"
|
|
# ifndef DEBUG_OUTPUT
|
|
# define DEBUG_OUTPUT "/dev/tty10"
|
|
# endif
|
|
# define debug(s) do { fprintf(dbf,s); fflush(dbf); } while (0)
|
|
FILE *dbf;
|
|
#else
|
|
# define debug(s) do { ; } while (0)
|
|
#endif
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
char *username = NULL; /* login name, given to /bin/login */
|
|
struct chardata chardata; /* will be set by get_logname() */
|
|
struct termios termios; /* terminal mode bits */
|
|
struct options options = {
|
|
.flags = F_ISSUE, /* show /etc/issue (SYSV_STYLE) */
|
|
.login = _PATH_LOGIN, /* default login program */
|
|
.tty = "tty1" /* default tty line */
|
|
};
|
|
struct issue issue = {
|
|
.mem = NULL,
|
|
};
|
|
char *login_argv[LOGIN_ARGV_MAX + 1];
|
|
int login_argc = 0;
|
|
struct sigaction sa, sa_hup, sa_quit, sa_int;
|
|
sigset_t set;
|
|
|
|
setlocale(LC_ALL, "");
|
|
bindtextdomain(PACKAGE, LOCALEDIR);
|
|
textdomain(PACKAGE);
|
|
|
|
/* In case vhangup(2) has to called */
|
|
sa.sa_handler = SIG_IGN;
|
|
sa.sa_flags = SA_RESTART;
|
|
sigemptyset (&sa.sa_mask);
|
|
sigaction(SIGHUP, &sa, &sa_hup);
|
|
sigaction(SIGQUIT, &sa, &sa_quit);
|
|
sigaction(SIGINT, &sa, &sa_int);
|
|
|
|
#ifdef DEBUGGING
|
|
dbf = fopen(DEBUG_OUTPUT, "w");
|
|
for (int i = 1; i < argc; i++) {
|
|
if (i > 1)
|
|
debug(" ");
|
|
debug(argv[i]);
|
|
}
|
|
debug("\n");
|
|
#endif /* DEBUGGING */
|
|
|
|
/* Parse command-line arguments. */
|
|
parse_args(argc, argv, &options);
|
|
|
|
login_argv[login_argc++] = options.login; /* set login program name */
|
|
|
|
/* Update the utmp file. */
|
|
#ifdef SYSV_STYLE
|
|
update_utmp(&options);
|
|
#endif
|
|
if (options.delay)
|
|
sleep(options.delay);
|
|
|
|
debug("calling open_tty\n");
|
|
|
|
/* Open the tty as standard { input, output, error }. */
|
|
open_tty(options.tty, &termios, &options);
|
|
|
|
/* Unmask SIGHUP if inherited */
|
|
sigemptyset(&set);
|
|
sigaddset(&set, SIGHUP);
|
|
sigprocmask(SIG_UNBLOCK, &set, NULL);
|
|
sigaction(SIGHUP, &sa_hup, NULL);
|
|
|
|
tcsetpgrp(STDIN_FILENO, getpid());
|
|
|
|
/* Default is to follow the current line speed and then default to 9600 */
|
|
if ((options.flags & F_VCONSOLE) == 0 && options.numspeed == 0) {
|
|
options.speeds[options.numspeed++] = bcode("9600");
|
|
options.flags |= F_KEEPSPEED;
|
|
}
|
|
|
|
/* Initialize the termios settings (raw mode, eight-bit, blocking i/o). */
|
|
debug("calling termio_init\n");
|
|
termio_init(&options, &termios);
|
|
|
|
/* Write the modem init string and DO NOT flush the buffers. */
|
|
if (serial_tty_option(&options, F_INITSTRING) &&
|
|
options.initstring && *options.initstring != '\0') {
|
|
debug("writing init string\n");
|
|
write_all(STDOUT_FILENO, options.initstring,
|
|
strlen(options.initstring));
|
|
}
|
|
|
|
if (options.flags & F_VCONSOLE || options.clocal != CLOCAL_MODE_ALWAYS)
|
|
/* Go to blocking mode unless -L is specified, this change
|
|
* affects stdout, stdin and stderr as all the file descriptors
|
|
* are created by dup(). */
|
|
fcntl(STDOUT_FILENO, F_SETFL,
|
|
fcntl(STDOUT_FILENO, F_GETFL, 0) & ~O_NONBLOCK);
|
|
|
|
/* Optionally detect the baud rate from the modem status message. */
|
|
debug("before autobaud\n");
|
|
if (serial_tty_option(&options, F_PARSE))
|
|
auto_baud(&termios);
|
|
|
|
/* Set the optional timer. */
|
|
if (options.timeout)
|
|
alarm(options.timeout);
|
|
|
|
/* Optionally wait for CR or LF before writing /etc/issue */
|
|
if (serial_tty_option(&options, F_WAITCRLF)) {
|
|
char ch;
|
|
|
|
debug("waiting for cr-lf\n");
|
|
while (read(STDIN_FILENO, &ch, 1) == 1) {
|
|
/* Strip "parity bit". */
|
|
ch &= 0x7f;
|
|
#ifdef DEBUGGING
|
|
fprintf(dbf, "read %c\n", ch);
|
|
#endif
|
|
if (ch == '\n' || ch == '\r')
|
|
break;
|
|
}
|
|
}
|
|
|
|
INIT_CHARDATA(&chardata);
|
|
|
|
if (options.autolog) {
|
|
debug("doing auto login\n");
|
|
username = options.autolog;
|
|
}
|
|
|
|
if (options.flags & F_NOPROMPT) { /* --skip-login */
|
|
eval_issue_file(&issue, &options, &termios);
|
|
print_issue_file(&issue, &options, &termios);
|
|
} else { /* regular (auto)login */
|
|
if (options.autolog) {
|
|
/* Autologin prompt */
|
|
eval_issue_file(&issue, &options, &termios);
|
|
do_prompt(&issue, &options, &termios);
|
|
printf(_("%s%s (automatic login)\n"), LOGIN, options.autolog);
|
|
} else {
|
|
/* Read the login name. */
|
|
debug("reading login name\n");
|
|
while ((username =
|
|
get_logname(&issue, &options, &termios, &chardata)) == NULL)
|
|
if ((options.flags & F_VCONSOLE) == 0 && options.numspeed)
|
|
next_speed(&options, &termios);
|
|
}
|
|
}
|
|
|
|
/* Disable timer. */
|
|
if (options.timeout)
|
|
alarm(0);
|
|
|
|
/* Finalize the termios settings. */
|
|
if ((options.flags & F_VCONSOLE) == 0)
|
|
termio_final(&options, &termios, &chardata);
|
|
else
|
|
reset_vc(&options, &termios, 1);
|
|
|
|
/* Now the newline character should be properly written. */
|
|
write_all(STDOUT_FILENO, "\r\n", 2);
|
|
|
|
sigaction(SIGQUIT, &sa_quit, NULL);
|
|
sigaction(SIGINT, &sa_int, NULL);
|
|
|
|
if (username)
|
|
check_username(username);
|
|
|
|
if (options.logopt) {
|
|
/*
|
|
* The --login-options completely overwrites the default
|
|
* way how agetty composes login(1) command line.
|
|
*/
|
|
login_options_to_argv(login_argv, &login_argc,
|
|
options.logopt, username);
|
|
} else {
|
|
if (options.flags & F_REMOTE) {
|
|
if (fakehost) {
|
|
login_argv[login_argc++] = "-h";
|
|
login_argv[login_argc++] = fakehost;
|
|
} else if (options.flags & F_NOHOSTNAME)
|
|
login_argv[login_argc++] = "-H";
|
|
}
|
|
if (username) {
|
|
if (options.autolog)
|
|
login_argv[login_argc++] = "-f";
|
|
else
|
|
login_argv[login_argc++] = "--";
|
|
login_argv[login_argc++] = username;
|
|
}
|
|
}
|
|
|
|
login_argv[login_argc] = NULL; /* last login argv */
|
|
|
|
if (options.chroot && chroot(options.chroot) < 0)
|
|
log_err(_("%s: can't change root directory %s: %m"),
|
|
options.tty, options.chroot);
|
|
if (options.chdir && chdir(options.chdir) < 0)
|
|
log_err(_("%s: can't change working directory %s: %m"),
|
|
options.tty, options.chdir);
|
|
if (options.nice && nice(options.nice) < 0)
|
|
log_warn(_("%s: can't change process priority: %m"),
|
|
options.tty);
|
|
|
|
free(options.osrelease);
|
|
#ifdef DEBUGGING
|
|
if (close_stream(dbf) != 0)
|
|
log_err("write failed: %s", DEBUG_OUTPUT);
|
|
#endif
|
|
|
|
/* Let the login program take care of password validation. */
|
|
execv(options.login, login_argv);
|
|
log_err(_("%s: can't exec %s: %m"), options.tty, login_argv[0]);
|
|
}
|
|
|
|
/*
|
|
* Returns : @str if \u not found
|
|
* : @username if @str equal to "\u"
|
|
* : newly allocated string if \u mixed with something other
|
|
*/
|
|
static char *replace_u(char *str, char *username)
|
|
{
|
|
char *entry = NULL, *p = str;
|
|
size_t usz = username ? strlen(username) : 0;
|
|
|
|
while (*p) {
|
|
size_t sz;
|
|
char *tp, *old = entry;
|
|
|
|
if (memcmp(p, "\\u", 2)) {
|
|
p++;
|
|
continue; /* no \u */
|
|
}
|
|
sz = strlen(str);
|
|
|
|
if (p == str && sz == 2) {
|
|
/* 'str' contains only '\u' */
|
|
free(old);
|
|
return username;
|
|
}
|
|
|
|
tp = entry = malloc(sz + usz);
|
|
if (!tp)
|
|
log_err(_("failed to allocate memory: %m"));
|
|
|
|
if (p != str) {
|
|
/* copy chars before \u */
|
|
memcpy(tp, str, p - str);
|
|
tp += p - str;
|
|
}
|
|
if (usz) {
|
|
/* copy username */
|
|
memcpy(tp, username, usz);
|
|
tp += usz;
|
|
}
|
|
if (*(p + 2))
|
|
/* copy chars after \u + \0 */
|
|
memcpy(tp, p + 2, sz - (p - str) - 1);
|
|
else
|
|
*tp = '\0';
|
|
|
|
p = tp;
|
|
str = entry;
|
|
free(old);
|
|
}
|
|
|
|
return entry ? entry : str;
|
|
}
|
|
|
|
static void login_options_to_argv(char *argv[], int *argc,
|
|
char *str, char *username)
|
|
{
|
|
char *p;
|
|
int i = *argc;
|
|
|
|
while (str && isspace(*str))
|
|
str++;
|
|
p = str;
|
|
|
|
while (p && *p && i < LOGIN_ARGV_MAX) {
|
|
if (isspace(*p)) {
|
|
*p = '\0';
|
|
while (isspace(*++p))
|
|
;
|
|
if (*p) {
|
|
argv[i++] = replace_u(str, username);
|
|
str = p;
|
|
}
|
|
} else
|
|
p++;
|
|
}
|
|
if (str && *str && i < LOGIN_ARGV_MAX)
|
|
argv[i++] = replace_u(str, username);
|
|
*argc = i;
|
|
}
|
|
|
|
static void output_version(void)
|
|
{
|
|
static const char *features[] = {
|
|
#ifdef DEBUGGING
|
|
"debug",
|
|
#endif
|
|
#ifdef CRTSCTS
|
|
"flow control",
|
|
#endif
|
|
#ifdef KDGKBLED
|
|
"hints",
|
|
#endif
|
|
#ifdef ISSUE_SUPPORT
|
|
"issue",
|
|
#endif
|
|
#ifdef ISSUEDIR_SUPPORT
|
|
"issue.d",
|
|
#endif
|
|
#ifdef KDGKBMODE
|
|
"keyboard mode",
|
|
#endif
|
|
#ifdef USE_PLYMOUTH_SUPPORT
|
|
"plymouth",
|
|
#endif
|
|
#ifdef AGETTY_RELOAD
|
|
"reload",
|
|
#endif
|
|
#ifdef USE_SYSLOG
|
|
"syslog",
|
|
#endif
|
|
#ifdef HAVE_WIDECHAR
|
|
"widechar",
|
|
#endif
|
|
NULL
|
|
};
|
|
unsigned int i;
|
|
|
|
printf( _("%s from %s"), program_invocation_short_name, PACKAGE_STRING);
|
|
fputs(" (", stdout);
|
|
for (i = 0; features[i]; i++) {
|
|
if (0 < i)
|
|
fputs(", ", stdout);
|
|
printf("%s", features[i]);
|
|
}
|
|
fputs(")\n", stdout);
|
|
}
|
|
|
|
#define is_speed(str) (strlen((str)) == strspn((str), "0123456789,"))
|
|
|
|
/* Parse command-line arguments. */
|
|
static void parse_args(int argc, char **argv, struct options *op)
|
|
{
|
|
int c;
|
|
|
|
enum {
|
|
VERSION_OPTION = CHAR_MAX + 1,
|
|
NOHINTS_OPTION,
|
|
NOHOSTNAME_OPTION,
|
|
LONGHOSTNAME_OPTION,
|
|
HELP_OPTION,
|
|
ERASE_CHARS_OPTION,
|
|
KILL_CHARS_OPTION,
|
|
RELOAD_OPTION,
|
|
LIST_SPEEDS_OPTION,
|
|
};
|
|
const struct option longopts[] = {
|
|
{ "8bits", no_argument, NULL, '8' },
|
|
{ "autologin", required_argument, NULL, 'a' },
|
|
{ "noreset", no_argument, NULL, 'c' },
|
|
{ "chdir", required_argument, NULL, 'C' },
|
|
{ "delay", required_argument, NULL, 'd' },
|
|
{ "remote", no_argument, NULL, 'E' },
|
|
{ "issue-file", required_argument, NULL, 'f' },
|
|
{ "flow-control", no_argument, NULL, 'h' },
|
|
{ "host", required_argument, NULL, 'H' },
|
|
{ "noissue", no_argument, NULL, 'i' },
|
|
{ "init-string", required_argument, NULL, 'I' },
|
|
{ "noclear", no_argument, NULL, 'J' },
|
|
{ "login-program", required_argument, NULL, 'l' },
|
|
{ "local-line", optional_argument, NULL, 'L' },
|
|
{ "extract-baud", no_argument, NULL, 'm' },
|
|
{ "list-speeds", no_argument, NULL, LIST_SPEEDS_OPTION },
|
|
{ "skip-login", no_argument, NULL, 'n' },
|
|
{ "nonewline", no_argument, NULL, 'N' },
|
|
{ "login-options", required_argument, NULL, 'o' },
|
|
{ "login-pause", no_argument, NULL, 'p' },
|
|
{ "nice", required_argument, NULL, 'P' },
|
|
{ "chroot", required_argument, NULL, 'r' },
|
|
{ "hangup", no_argument, NULL, 'R' },
|
|
{ "keep-baud", no_argument, NULL, 's' },
|
|
{ "timeout", required_argument, NULL, 't' },
|
|
{ "detect-case", no_argument, NULL, 'U' },
|
|
{ "wait-cr", no_argument, NULL, 'w' },
|
|
{ "nohints", no_argument, NULL, NOHINTS_OPTION },
|
|
{ "nohostname", no_argument, NULL, NOHOSTNAME_OPTION },
|
|
{ "long-hostname", no_argument, NULL, LONGHOSTNAME_OPTION },
|
|
{ "reload", no_argument, NULL, RELOAD_OPTION },
|
|
{ "version", no_argument, NULL, VERSION_OPTION },
|
|
{ "help", no_argument, NULL, HELP_OPTION },
|
|
{ "erase-chars", required_argument, NULL, ERASE_CHARS_OPTION },
|
|
{ "kill-chars", required_argument, NULL, KILL_CHARS_OPTION },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
|
|
while ((c = getopt_long(argc, argv,
|
|
"8a:cC:d:Ef:hH:iI:Jl:L::mnNo:pP:r:Rst:Uw", longopts,
|
|
NULL)) != -1) {
|
|
switch (c) {
|
|
case '8':
|
|
op->flags |= F_EIGHTBITS;
|
|
break;
|
|
case 'a':
|
|
op->autolog = optarg;
|
|
break;
|
|
case 'c':
|
|
op->flags |= F_KEEPCFLAGS;
|
|
break;
|
|
case 'C':
|
|
op->chdir = optarg;
|
|
break;
|
|
case 'd':
|
|
op->delay = strtou32_or_err(optarg, _("invalid delay argument"));
|
|
break;
|
|
case 'E':
|
|
op->flags |= F_REMOTE;
|
|
break;
|
|
case 'f':
|
|
op->issue = optarg;
|
|
break;
|
|
case 'h':
|
|
op->flags |= F_RTSCTS;
|
|
break;
|
|
case 'H':
|
|
fakehost = optarg;
|
|
break;
|
|
case 'i':
|
|
op->flags &= ~F_ISSUE;
|
|
break;
|
|
case 'I':
|
|
init_special_char(optarg, op);
|
|
op->flags |= F_INITSTRING;
|
|
break;
|
|
case 'J':
|
|
op->flags |= F_NOCLEAR;
|
|
break;
|
|
case 'l':
|
|
op->login = optarg;
|
|
break;
|
|
case 'L':
|
|
/* -L and -L=always have the same meaning */
|
|
op->clocal = CLOCAL_MODE_ALWAYS;
|
|
if (optarg) {
|
|
if (strcmp(optarg, "=always") == 0)
|
|
op->clocal = CLOCAL_MODE_ALWAYS;
|
|
else if (strcmp(optarg, "=never") == 0)
|
|
op->clocal = CLOCAL_MODE_NEVER;
|
|
else if (strcmp(optarg, "=auto") == 0)
|
|
op->clocal = CLOCAL_MODE_AUTO;
|
|
else
|
|
log_err(_("invalid argument of --local-line"));
|
|
}
|
|
break;
|
|
case 'm':
|
|
op->flags |= F_PARSE;
|
|
break;
|
|
case 'n':
|
|
op->flags |= F_NOPROMPT;
|
|
break;
|
|
case 'N':
|
|
op->flags |= F_NONL;
|
|
break;
|
|
case 'o':
|
|
op->logopt = optarg;
|
|
break;
|
|
case 'p':
|
|
op->flags |= F_LOGINPAUSE;
|
|
break;
|
|
case 'P':
|
|
op->nice = strtos32_or_err(optarg, _("invalid nice argument"));
|
|
break;
|
|
case 'r':
|
|
op->chroot = optarg;
|
|
break;
|
|
case 'R':
|
|
op->flags |= F_HANGUP;
|
|
break;
|
|
case 's':
|
|
op->flags |= F_KEEPSPEED;
|
|
break;
|
|
case 't':
|
|
op->timeout = strtou32_or_err(optarg, _("invalid timeout argument"));
|
|
break;
|
|
case 'U':
|
|
op->flags |= F_LCUC;
|
|
break;
|
|
case 'w':
|
|
op->flags |= F_WAITCRLF;
|
|
break;
|
|
case NOHINTS_OPTION:
|
|
op->flags |= F_NOHINTS;
|
|
break;
|
|
case NOHOSTNAME_OPTION:
|
|
op->flags |= F_NOHOSTNAME;
|
|
break;
|
|
case LONGHOSTNAME_OPTION:
|
|
op->flags |= F_LONGHNAME;
|
|
break;
|
|
case ERASE_CHARS_OPTION:
|
|
op->erasechars = optarg;
|
|
break;
|
|
case KILL_CHARS_OPTION:
|
|
op->killchars = optarg;
|
|
break;
|
|
case RELOAD_OPTION:
|
|
reload_agettys();
|
|
exit(EXIT_SUCCESS);
|
|
case LIST_SPEEDS_OPTION:
|
|
list_speeds();
|
|
exit(EXIT_SUCCESS);
|
|
case VERSION_OPTION:
|
|
output_version();
|
|
exit(EXIT_SUCCESS);
|
|
case HELP_OPTION:
|
|
usage();
|
|
default:
|
|
errtryhelp(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
debug("after getopt loop\n");
|
|
|
|
if (argc < optind + 1) {
|
|
log_warn(_("not enough arguments"));
|
|
errx(EXIT_FAILURE, _("not enough arguments"));
|
|
}
|
|
|
|
/* Accept "tty", "baudrate tty", and "tty baudrate". */
|
|
if (is_speed(argv[optind])) {
|
|
/* Assume BSD style speed. */
|
|
parse_speeds(op, argv[optind++]);
|
|
if (argc < optind + 1) {
|
|
log_warn(_("not enough arguments"));
|
|
errx(EXIT_FAILURE, _("not enough arguments"));
|
|
}
|
|
op->tty = argv[optind++];
|
|
} else {
|
|
op->tty = argv[optind++];
|
|
if (argc > optind) {
|
|
char *v = argv[optind];
|
|
if (is_speed(v)) {
|
|
parse_speeds(op, v);
|
|
optind++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* On virtual console remember the line which is used for */
|
|
if (strncmp(op->tty, "tty", 3) == 0 &&
|
|
strspn(op->tty + 3, "0123456789") == strlen(op->tty+3))
|
|
op->vcline = op->tty+3;
|
|
|
|
if (argc > optind && argv[optind])
|
|
op->term = argv[optind];
|
|
|
|
debug("exiting parseargs\n");
|
|
}
|
|
|
|
/* Parse alternate baud rates. */
|
|
static void parse_speeds(struct options *op, char *arg)
|
|
{
|
|
char *cp;
|
|
char *str = strdup(arg);
|
|
|
|
if (!str)
|
|
log_err(_("failed to allocate memory: %m"));
|
|
|
|
debug("entered parse_speeds:\n");
|
|
for (cp = strtok(str, ","); cp != NULL; cp = strtok((char *)0, ",")) {
|
|
if ((op->speeds[op->numspeed++] = bcode(cp)) <= 0)
|
|
log_err(_("bad speed: %s"), cp);
|
|
if (op->numspeed >= MAX_SPEED)
|
|
log_err(_("too many alternate speeds"));
|
|
}
|
|
debug("exiting parsespeeds\n");
|
|
free(str);
|
|
}
|
|
|
|
#ifdef SYSV_STYLE
|
|
|
|
/* Update our utmp entry. */
|
|
static void update_utmp(struct options *op)
|
|
{
|
|
struct utmpx ut;
|
|
time_t t;
|
|
pid_t pid = getpid();
|
|
pid_t sid = getsid(0);
|
|
char *vcline = op->vcline;
|
|
char *line = op->tty;
|
|
struct utmpx *utp;
|
|
|
|
/*
|
|
* The utmp file holds miscellaneous information about things started by
|
|
* /sbin/init and other system-related events. Our purpose is to update
|
|
* the utmp entry for the current process, in particular the process type
|
|
* and the tty line we are listening to. Return successfully only if the
|
|
* utmp file can be opened for update, and if we are able to find our
|
|
* entry in the utmp file.
|
|
*/
|
|
utmpxname(_PATH_UTMP);
|
|
setutxent();
|
|
|
|
/*
|
|
* Find my pid in utmp.
|
|
*
|
|
* FIXME: Earlier (when was that?) code here tested only utp->ut_type !=
|
|
* INIT_PROCESS, so maybe the >= here should be >.
|
|
*
|
|
* FIXME: The present code is taken from login.c, so if this is changed,
|
|
* maybe login has to be changed as well (is this true?).
|
|
*/
|
|
while ((utp = getutxent()))
|
|
if (utp->ut_pid == pid
|
|
&& utp->ut_type >= INIT_PROCESS
|
|
&& utp->ut_type <= DEAD_PROCESS)
|
|
break;
|
|
|
|
if (utp) {
|
|
memcpy(&ut, utp, sizeof(ut));
|
|
} else {
|
|
/* Some inits do not initialize utmp. */
|
|
memset(&ut, 0, sizeof(ut));
|
|
if (vcline && *vcline)
|
|
/* Standard virtual console devices */
|
|
str2memcpy(ut.ut_id, vcline, sizeof(ut.ut_id));
|
|
else {
|
|
size_t len = strlen(line);
|
|
char * ptr;
|
|
if (len >= sizeof(ut.ut_id))
|
|
ptr = line + len - sizeof(ut.ut_id);
|
|
else
|
|
ptr = line;
|
|
str2memcpy(ut.ut_id, ptr, sizeof(ut.ut_id));
|
|
}
|
|
}
|
|
|
|
str2memcpy(ut.ut_user, "LOGIN", sizeof(ut.ut_user));
|
|
str2memcpy(ut.ut_line, line, sizeof(ut.ut_line));
|
|
if (fakehost)
|
|
str2memcpy(ut.ut_host, fakehost, sizeof(ut.ut_host));
|
|
time(&t);
|
|
ut.ut_tv.tv_sec = t;
|
|
ut.ut_type = LOGIN_PROCESS;
|
|
ut.ut_pid = pid;
|
|
ut.ut_session = sid;
|
|
|
|
pututxline(&ut);
|
|
endutxent();
|
|
|
|
updwtmpx(_PATH_WTMP, &ut);
|
|
}
|
|
|
|
#endif /* SYSV_STYLE */
|
|
|
|
/* Set up tty as stdin, stdout & stderr. */
|
|
static void open_tty(char *tty, struct termios *tp, struct options *op)
|
|
{
|
|
const pid_t pid = getpid();
|
|
int closed = 0;
|
|
#ifndef KDGKBMODE
|
|
int serial;
|
|
#endif
|
|
|
|
/* Set up new standard input, unless we are given an already opened port. */
|
|
|
|
if (strcmp(tty, "-") != 0) {
|
|
char buf[PATH_MAX+1];
|
|
struct group *gr = NULL;
|
|
struct stat st;
|
|
int fd, len;
|
|
pid_t tid;
|
|
gid_t gid = 0;
|
|
|
|
/* Use tty group if available */
|
|
if ((gr = getgrnam("tty")))
|
|
gid = gr->gr_gid;
|
|
|
|
len = snprintf(buf, sizeof(buf), "/dev/%s", tty);
|
|
if (len < 0 || (size_t)len >= sizeof(buf))
|
|
log_err(_("/dev/%s: cannot open as standard input: %m"), tty);
|
|
|
|
/* Open the tty as standard input. */
|
|
if ((fd = open(buf, O_RDWR|O_NOCTTY|O_NONBLOCK, 0)) < 0)
|
|
log_err(_("/dev/%s: cannot open as standard input: %m"), tty);
|
|
|
|
/*
|
|
* There is always a race between this reset and the call to
|
|
* vhangup() that s.o. can use to get access to your tty.
|
|
* Linux login(1) will change tty permissions. Use root owner and group
|
|
* with permission -rw------- for the period between getty and login.
|
|
*/
|
|
if (fchown(fd, 0, gid) || fchmod(fd, (gid ? 0620 : 0600))) {
|
|
if (errno == EROFS)
|
|
log_warn("%s: %m", buf);
|
|
else
|
|
log_err("%s: %m", buf);
|
|
}
|
|
|
|
/* Sanity checks... */
|
|
if (fstat(fd, &st) < 0)
|
|
log_err("%s: %m", buf);
|
|
if ((st.st_mode & S_IFMT) != S_IFCHR)
|
|
log_err(_("/dev/%s: not a character device"), tty);
|
|
if (!isatty(fd))
|
|
log_err(_("/dev/%s: not a tty"), tty);
|
|
|
|
if (((tid = tcgetsid(fd)) < 0) || (pid != tid)) {
|
|
if (ioctl(fd, TIOCSCTTY, 1) == -1)
|
|
log_warn(_("/dev/%s: cannot get controlling tty: %m"), tty);
|
|
}
|
|
|
|
close(STDIN_FILENO);
|
|
errno = 0;
|
|
|
|
if (op->flags & F_HANGUP) {
|
|
|
|
if (ioctl(fd, TIOCNOTTY))
|
|
debug("TIOCNOTTY ioctl failed\n");
|
|
|
|
/*
|
|
* Let's close all file descriptors before vhangup
|
|
* https://lkml.org/lkml/2012/6/5/145
|
|
*/
|
|
close(fd);
|
|
close(STDOUT_FILENO);
|
|
close(STDERR_FILENO);
|
|
errno = 0;
|
|
closed = 1;
|
|
|
|
if (vhangup())
|
|
log_err(_("/dev/%s: vhangup() failed: %m"), tty);
|
|
} else
|
|
close(fd);
|
|
|
|
debug("open(2)\n");
|
|
if (open(buf, O_RDWR|O_NOCTTY|O_NONBLOCK, 0) != 0)
|
|
log_err(_("/dev/%s: cannot open as standard input: %m"), tty);
|
|
|
|
if (((tid = tcgetsid(STDIN_FILENO)) < 0) || (pid != tid)) {
|
|
if (ioctl(STDIN_FILENO, TIOCSCTTY, 1) == -1)
|
|
log_warn(_("/dev/%s: cannot get controlling tty: %m"), tty);
|
|
}
|
|
|
|
} else {
|
|
|
|
/*
|
|
* Standard input should already be connected to an open port. Make
|
|
* sure it is open for read/write.
|
|
*/
|
|
|
|
if ((fcntl(STDIN_FILENO, F_GETFL, 0) & O_RDWR) != O_RDWR)
|
|
log_err(_("%s: not open for read/write"), tty);
|
|
|
|
}
|
|
|
|
if (tcsetpgrp(STDIN_FILENO, pid))
|
|
log_warn(_("/dev/%s: cannot set process group: %m"), tty);
|
|
|
|
/* Get rid of the present outputs. */
|
|
if (!closed) {
|
|
close(STDOUT_FILENO);
|
|
close(STDERR_FILENO);
|
|
errno = 0;
|
|
}
|
|
|
|
/* Set up standard output and standard error file descriptors. */
|
|
debug("duping\n");
|
|
|
|
/* set up stdout and stderr */
|
|
if (dup(STDIN_FILENO) != 1 || dup(STDIN_FILENO) != 2)
|
|
log_err(_("%s: dup problem: %m"), tty);
|
|
|
|
/* make stdio unbuffered for slow modem lines */
|
|
setvbuf(stdout, NULL, _IONBF, 0);
|
|
|
|
/*
|
|
* The following ioctl will fail if stdin is not a tty, but also when
|
|
* there is noise on the modem control lines. In the latter case, the
|
|
* common course of action is (1) fix your cables (2) give the modem
|
|
* more time to properly reset after hanging up.
|
|
*
|
|
* SunOS users can achieve (2) by patching the SunOS kernel variable
|
|
* "zsadtrlow" to a larger value; 5 seconds seems to be a good value.
|
|
* http://www.sunmanagers.org/archives/1993/0574.html
|
|
*/
|
|
memset(tp, 0, sizeof(struct termios));
|
|
if (tcgetattr(STDIN_FILENO, tp) < 0)
|
|
log_err(_("%s: failed to get terminal attributes: %m"), tty);
|
|
|
|
#if defined (__s390__) || defined (__s390x__)
|
|
if (!op->term) {
|
|
/*
|
|
* Special terminal on first serial line on a S/390(x) which
|
|
* is due legacy reasons a block terminal of type 3270 or
|
|
* higher. Whereas the second serial line on a S/390(x) is
|
|
* a real character terminal which is compatible with VT220.
|
|
*/
|
|
if (strcmp(op->tty, "ttyS0") == 0) /* linux/drivers/s390/char/con3215.c */
|
|
op->term = DEFAULT_TTYS0;
|
|
else if (strncmp(op->tty, "3270/tty", 8) == 0) /* linux/drivers/s390/char/con3270.c */
|
|
op->term = DEFAULT_TTY32;
|
|
else if (strcmp(op->tty, "ttyS1") == 0) /* linux/drivers/s390/char/sclp_vt220.c */
|
|
op->term = DEFAULT_TTYS1;
|
|
}
|
|
#endif
|
|
|
|
#if defined(__FreeBSD_kernel__)
|
|
login_tty (0);
|
|
#endif
|
|
|
|
/*
|
|
* Detect if this is a virtual console or serial/modem line.
|
|
* In case of a virtual console the ioctl KDGKBMODE succeeds
|
|
* whereas on other lines it will fails.
|
|
*/
|
|
#ifdef KDGKBMODE
|
|
if (ioctl(STDIN_FILENO, KDGKBMODE, &op->kbmode) == 0)
|
|
#else
|
|
if (ioctl(STDIN_FILENO, TIOCMGET, &serial) < 0 && (errno == EINVAL))
|
|
#endif
|
|
{
|
|
op->flags |= F_VCONSOLE;
|
|
if (!op->term)
|
|
op->term = DEFAULT_VCTERM;
|
|
} else {
|
|
#ifdef K_RAW
|
|
op->kbmode = K_RAW;
|
|
#endif
|
|
if (!op->term)
|
|
op->term = DEFAULT_STERM;
|
|
}
|
|
|
|
if (setenv("TERM", op->term, 1) != 0)
|
|
log_err(_("failed to set the %s environment variable"), "TERM");
|
|
}
|
|
|
|
/* Initialize termios settings. */
|
|
static void termio_clear(int fd)
|
|
{
|
|
/*
|
|
* Do not write a full reset (ESC c) because this destroys
|
|
* the unicode mode again if the terminal was in unicode
|
|
* mode. Also it clears the CONSOLE_MAGIC features which
|
|
* are required for some languages/console-fonts.
|
|
* Just put the cursor to the home position (ESC [ H),
|
|
* erase everything below the cursor (ESC [ J), and set the
|
|
* scrolling region to the full window (ESC [ r)
|
|
*/
|
|
write_all(fd, "\033[r\033[H\033[J", 9);
|
|
}
|
|
|
|
/* Initialize termios settings. */
|
|
static void termio_init(struct options *op, struct termios *tp)
|
|
{
|
|
speed_t ispeed, ospeed;
|
|
struct winsize ws;
|
|
#ifdef USE_PLYMOUTH_SUPPORT
|
|
struct termios lock;
|
|
int i = (plymouth_command(MAGIC_PING) == 0) ? PLYMOUTH_TERMIOS_FLAGS_DELAY : 0;
|
|
if (i)
|
|
plymouth_command(MAGIC_QUIT);
|
|
while (i-- > 0) {
|
|
/*
|
|
* Even with TTYReset=no it seems with systemd or plymouth
|
|
* the termios flags become changed from under the first
|
|
* agetty on a serial system console as the flags are locked.
|
|
*/
|
|
memset(&lock, 0, sizeof(struct termios));
|
|
if (ioctl(STDIN_FILENO, TIOCGLCKTRMIOS, &lock) < 0)
|
|
break;
|
|
if (!lock.c_iflag && !lock.c_oflag && !lock.c_cflag && !lock.c_lflag)
|
|
break;
|
|
debug("termios locked\n");
|
|
sleep(1);
|
|
}
|
|
memset(&lock, 0, sizeof(struct termios));
|
|
ioctl(STDIN_FILENO, TIOCSLCKTRMIOS, &lock);
|
|
#endif
|
|
|
|
if (op->flags & F_VCONSOLE) {
|
|
#if defined(IUTF8) && defined(KDGKBMODE)
|
|
switch(op->kbmode) {
|
|
case K_UNICODE:
|
|
setlocale(LC_CTYPE, "C.UTF-8");
|
|
op->flags |= F_UTF8;
|
|
break;
|
|
case K_RAW:
|
|
case K_MEDIUMRAW:
|
|
case K_XLATE:
|
|
default:
|
|
setlocale(LC_CTYPE, "POSIX");
|
|
op->flags &= ~F_UTF8;
|
|
break;
|
|
}
|
|
#else
|
|
setlocale(LC_CTYPE, "POSIX");
|
|
op->flags &= ~F_UTF8;
|
|
#endif
|
|
reset_vc(op, tp, 0);
|
|
|
|
if ((tp->c_cflag & (CS8|PARODD|PARENB)) == CS8)
|
|
op->flags |= F_EIGHTBITS;
|
|
|
|
if ((op->flags & F_NOCLEAR) == 0)
|
|
termio_clear(STDOUT_FILENO);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Serial line
|
|
*/
|
|
|
|
if (op->flags & F_KEEPSPEED || !op->numspeed) {
|
|
/* Save the original setting. */
|
|
ispeed = cfgetispeed(tp);
|
|
ospeed = cfgetospeed(tp);
|
|
|
|
if (!ispeed) ispeed = TTYDEF_SPEED;
|
|
if (!ospeed) ospeed = TTYDEF_SPEED;
|
|
|
|
} else {
|
|
ospeed = ispeed = op->speeds[FIRST_SPEED];
|
|
}
|
|
|
|
/*
|
|
* Initial termios settings: 8-bit characters, raw-mode, blocking i/o.
|
|
* Special characters are set after we have read the login name; all
|
|
* reads will be done in raw mode anyway. Errors will be dealt with
|
|
* later on.
|
|
*/
|
|
|
|
/* The default is set c_iflag in termio_final() according to chardata.
|
|
* Unfortunately, the chardata are not set according to the serial line
|
|
* if --autolog is enabled. In this case we do not read from the line
|
|
* at all. The best what we can do in this case is to keep c_iflag
|
|
* unmodified for --autolog.
|
|
*/
|
|
if (!op->autolog) {
|
|
#ifdef IUTF8
|
|
tp->c_iflag = tp->c_iflag & IUTF8;
|
|
if (tp->c_iflag & IUTF8)
|
|
op->flags |= F_UTF8;
|
|
#else
|
|
tp->c_iflag = 0;
|
|
#endif
|
|
}
|
|
|
|
tp->c_lflag = 0;
|
|
tp->c_oflag &= OPOST | ONLCR;
|
|
|
|
if ((op->flags & F_KEEPCFLAGS) == 0)
|
|
tp->c_cflag = CS8 | HUPCL | CREAD | (tp->c_cflag & CLOCAL);
|
|
|
|
/*
|
|
* Note that the speed is stored in the c_cflag termios field, so we have
|
|
* set the speed always when the cflag is reset.
|
|
*/
|
|
cfsetispeed(tp, ispeed);
|
|
cfsetospeed(tp, ospeed);
|
|
|
|
/* The default is to follow setting from kernel, but it's possible
|
|
* to explicitly remove/add CLOCAL flag by -L[=<mode>]*/
|
|
switch (op->clocal) {
|
|
case CLOCAL_MODE_ALWAYS:
|
|
tp->c_cflag |= CLOCAL; /* -L or -L=always */
|
|
break;
|
|
case CLOCAL_MODE_NEVER:
|
|
tp->c_cflag &= ~CLOCAL; /* -L=never */
|
|
break;
|
|
case CLOCAL_MODE_AUTO: /* -L=auto */
|
|
break;
|
|
}
|
|
|
|
#ifdef HAVE_STRUCT_TERMIOS_C_LINE
|
|
tp->c_line = 0;
|
|
#endif
|
|
tp->c_cc[VMIN] = 1;
|
|
tp->c_cc[VTIME] = 0;
|
|
|
|
/* Check for terminal size and if not found set default */
|
|
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0) {
|
|
if (ws.ws_row == 0)
|
|
ws.ws_row = 24;
|
|
if (ws.ws_col == 0)
|
|
ws.ws_col = 80;
|
|
if (ioctl(STDIN_FILENO, TIOCSWINSZ, &ws))
|
|
debug("TIOCSWINSZ ioctl failed\n");
|
|
}
|
|
|
|
/* Optionally enable hardware flow control. */
|
|
#ifdef CRTSCTS
|
|
if (op->flags & F_RTSCTS)
|
|
tp->c_cflag |= CRTSCTS;
|
|
#endif
|
|
/* Flush input and output queues, important for modems! */
|
|
tcflush(STDIN_FILENO, TCIOFLUSH);
|
|
|
|
if (tcsetattr(STDIN_FILENO, TCSANOW, tp))
|
|
log_warn(_("setting terminal attributes failed: %m"));
|
|
|
|
/* Go to blocking input even in local mode. */
|
|
fcntl(STDIN_FILENO, F_SETFL,
|
|
fcntl(STDIN_FILENO, F_GETFL, 0) & ~O_NONBLOCK);
|
|
|
|
debug("term_io 2\n");
|
|
}
|
|
|
|
/* Reset virtual console on stdin to its defaults */
|
|
static void reset_vc(const struct options *op, struct termios *tp, int canon)
|
|
{
|
|
int fl = 0;
|
|
|
|
fl |= (op->flags & F_KEEPCFLAGS) == 0 ? 0 : UL_TTY_KEEPCFLAGS;
|
|
fl |= (op->flags & F_UTF8) == 0 ? 0 : UL_TTY_UTF8;
|
|
|
|
reset_virtual_console(tp, fl);
|
|
|
|
#ifdef AGETTY_RELOAD
|
|
/*
|
|
* Discard all the flags that makes the line go canonical with echoing.
|
|
* We need to know when the user starts typing.
|
|
*/
|
|
if (canon == 0)
|
|
tp->c_lflag = 0;
|
|
#endif
|
|
|
|
if (tcsetattr(STDIN_FILENO, TCSADRAIN, tp))
|
|
log_warn(_("setting terminal attributes failed: %m"));
|
|
|
|
/* Go to blocking input even in local mode. */
|
|
fcntl(STDIN_FILENO, F_SETFL,
|
|
fcntl(STDIN_FILENO, F_GETFL, 0) & ~O_NONBLOCK);
|
|
}
|
|
|
|
/* Extract baud rate from modem status message. */
|
|
static void auto_baud(struct termios *tp)
|
|
{
|
|
speed_t speed;
|
|
int vmin;
|
|
unsigned iflag;
|
|
char buf[BUFSIZ];
|
|
char *bp;
|
|
int nread;
|
|
|
|
/*
|
|
* This works only if the modem produces its status code AFTER raising
|
|
* the DCD line, and if the computer is fast enough to set the proper
|
|
* baud rate before the message has gone by. We expect a message of the
|
|
* following format:
|
|
*
|
|
* <junk><number><junk>
|
|
*
|
|
* The number is interpreted as the baud rate of the incoming call. If the
|
|
* modem does not tell us the baud rate within one second, we will keep
|
|
* using the current baud rate. It is advisable to enable BREAK
|
|
* processing (comma-separated list of baud rates) if the processing of
|
|
* modem status messages is enabled.
|
|
*/
|
|
|
|
/*
|
|
* Use 7-bit characters, don't block if input queue is empty. Errors will
|
|
* be dealt with later on.
|
|
*/
|
|
iflag = tp->c_iflag;
|
|
/* Enable 8th-bit stripping. */
|
|
tp->c_iflag |= ISTRIP;
|
|
vmin = tp->c_cc[VMIN];
|
|
/* Do not block when queue is empty. */
|
|
tp->c_cc[VMIN] = 0;
|
|
tcsetattr(STDIN_FILENO, TCSANOW, tp);
|
|
|
|
/*
|
|
* Wait for a while, then read everything the modem has said so far and
|
|
* try to extract the speed of the dial-in call.
|
|
*/
|
|
sleep(1);
|
|
if ((nread = read(STDIN_FILENO, buf, sizeof(buf) - 1)) > 0) {
|
|
buf[nread] = '\0';
|
|
for (bp = buf; bp < buf + nread; bp++)
|
|
if (c_isascii(*bp) && isdigit(*bp)) {
|
|
if ((speed = bcode(bp))) {
|
|
cfsetispeed(tp, speed);
|
|
cfsetospeed(tp, speed);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Restore terminal settings. Errors will be dealt with later on. */
|
|
tp->c_iflag = iflag;
|
|
tp->c_cc[VMIN] = vmin;
|
|
tcsetattr(STDIN_FILENO, TCSANOW, tp);
|
|
}
|
|
|
|
static char *xgethostname(void)
|
|
{
|
|
char *name;
|
|
size_t sz = get_hostname_max() + 1;
|
|
|
|
name = malloc(sizeof(char) * sz);
|
|
if (!name)
|
|
log_err(_("failed to allocate memory: %m"));
|
|
|
|
if (gethostname(name, sz) != 0) {
|
|
free(name);
|
|
return NULL;
|
|
}
|
|
name[sz - 1] = '\0';
|
|
return name;
|
|
}
|
|
|
|
static char *xgetdomainname(void)
|
|
{
|
|
#ifdef HAVE_GETDOMAINNAME
|
|
char *name;
|
|
const size_t sz = get_hostname_max() + 1;
|
|
|
|
name = malloc(sizeof(char) * sz);
|
|
if (!name)
|
|
log_err(_("failed to allocate memory: %m"));
|
|
|
|
if (getdomainname(name, sz) != 0) {
|
|
free(name);
|
|
return NULL;
|
|
}
|
|
name[sz - 1] = '\0';
|
|
return name;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
|
|
static char *read_os_release(struct options *op, const char *varname)
|
|
{
|
|
int fd = -1;
|
|
struct stat st;
|
|
size_t varsz = strlen(varname);
|
|
char *p, *buf = NULL, *ret = NULL;
|
|
|
|
/* read the file only once */
|
|
if (!op->osrelease) {
|
|
fd = open(_PATH_OS_RELEASE_ETC, O_RDONLY);
|
|
if (fd == -1) {
|
|
fd = open(_PATH_OS_RELEASE_USR, O_RDONLY);
|
|
if (fd == -1) {
|
|
log_warn(_("cannot open os-release file"));
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (fstat(fd, &st) < 0 || st.st_size > 4 * 1024 * 1024)
|
|
goto done;
|
|
|
|
op->osrelease = malloc(st.st_size + 1);
|
|
if (!op->osrelease)
|
|
log_err(_("failed to allocate memory: %m"));
|
|
if (read_all(fd, op->osrelease, st.st_size) != (ssize_t) st.st_size) {
|
|
free(op->osrelease);
|
|
op->osrelease = NULL;
|
|
goto done;
|
|
}
|
|
op->osrelease[st.st_size] = 0;
|
|
}
|
|
buf = strdup(op->osrelease);
|
|
if (!buf)
|
|
log_err(_("failed to allocate memory: %m"));
|
|
p = buf;
|
|
|
|
for (;;) {
|
|
char *eol, *eon;
|
|
|
|
p += strspn(p, "\n\r");
|
|
p += strspn(p, " \t\n\r");
|
|
if (!*p)
|
|
break;
|
|
if (strspn(p, "#;\n") != 0) {
|
|
p += strcspn(p, "\n\r");
|
|
continue;
|
|
}
|
|
if (strncmp(p, varname, varsz) != 0) {
|
|
p += strcspn(p, "\n\r");
|
|
continue;
|
|
}
|
|
p += varsz;
|
|
p += strspn(p, " \t\n\r");
|
|
|
|
if (*p != '=')
|
|
continue;
|
|
|
|
p += strspn(p, " \t\n\r=\"");
|
|
eol = p + strcspn(p, "\n\r");
|
|
*eol = '\0';
|
|
eon = eol-1;
|
|
while (eon > p) {
|
|
if (*eon == '\t' || *eon == ' ') {
|
|
eon--;
|
|
continue;
|
|
}
|
|
if (*eon == '"') {
|
|
*eon = '\0';
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
free(ret);
|
|
ret = strdup(p);
|
|
if (!ret)
|
|
log_err(_("failed to allocate memory: %m"));
|
|
p = eol + 1;
|
|
}
|
|
done:
|
|
free(buf);
|
|
if (fd >= 0)
|
|
close(fd);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef AGETTY_RELOAD
|
|
static void open_netlink(void)
|
|
{
|
|
struct sockaddr_nl addr = { 0, };
|
|
int sock;
|
|
|
|
if (netlink_fd != AGETTY_RELOAD_FDNONE)
|
|
return;
|
|
|
|
sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
|
if (sock >= 0) {
|
|
addr.nl_family = AF_NETLINK;
|
|
addr.nl_pid = getpid();
|
|
addr.nl_groups = netlink_groups;
|
|
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
|
|
close(sock);
|
|
else
|
|
netlink_fd = sock;
|
|
}
|
|
}
|
|
|
|
static int process_netlink_msg(int *triggered)
|
|
{
|
|
char buf[4096];
|
|
struct sockaddr_nl snl;
|
|
struct nlmsghdr *h;
|
|
int rc;
|
|
|
|
struct iovec iov = {
|
|
.iov_base = buf,
|
|
.iov_len = sizeof(buf)
|
|
};
|
|
struct msghdr msg = {
|
|
.msg_name = &snl,
|
|
.msg_namelen = sizeof(snl),
|
|
.msg_iov = &iov,
|
|
.msg_iovlen = 1,
|
|
.msg_control = NULL,
|
|
.msg_controllen = 0,
|
|
.msg_flags = 0
|
|
};
|
|
|
|
rc = recvmsg(netlink_fd, &msg, MSG_DONTWAIT);
|
|
if (rc < 0) {
|
|
if (errno == EWOULDBLOCK || errno == EAGAIN)
|
|
return 0;
|
|
|
|
/* Failure, just stop listening for changes */
|
|
close(netlink_fd);
|
|
netlink_fd = AGETTY_RELOAD_FDNONE;
|
|
return 0;
|
|
}
|
|
|
|
for (h = (struct nlmsghdr *)buf; NLMSG_OK(h, (unsigned int)rc); h = NLMSG_NEXT(h, rc)) {
|
|
if (h->nlmsg_type == NLMSG_DONE ||
|
|
h->nlmsg_type == NLMSG_ERROR) {
|
|
close(netlink_fd);
|
|
netlink_fd = AGETTY_RELOAD_FDNONE;
|
|
return 0;
|
|
}
|
|
|
|
*triggered = 1;
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int process_netlink(void)
|
|
{
|
|
int triggered = 0;
|
|
while (process_netlink_msg(&triggered));
|
|
return triggered;
|
|
}
|
|
|
|
static int wait_for_term_input(int fd)
|
|
{
|
|
char buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
|
|
fd_set rfds;
|
|
|
|
if (inotify_fd == AGETTY_RELOAD_FDNONE) {
|
|
/* make sure the reload trigger file exists */
|
|
int reload_fd = open(AGETTY_RELOAD_FILENAME,
|
|
O_CREAT|O_CLOEXEC|O_RDONLY,
|
|
S_IRUSR|S_IWUSR);
|
|
|
|
/* initialize reload trigger inotify stuff */
|
|
if (reload_fd >= 0) {
|
|
inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
|
|
if (inotify_fd > 0)
|
|
inotify_add_watch(inotify_fd, AGETTY_RELOAD_FILENAME,
|
|
IN_ATTRIB | IN_MODIFY);
|
|
|
|
close(reload_fd);
|
|
} else
|
|
log_warn(_("failed to create reload file: %s: %m"),
|
|
AGETTY_RELOAD_FILENAME);
|
|
}
|
|
|
|
while (1) {
|
|
int nfds = fd;
|
|
|
|
FD_ZERO(&rfds);
|
|
FD_SET(fd, &rfds);
|
|
|
|
if (inotify_fd >= 0) {
|
|
FD_SET(inotify_fd, &rfds);
|
|
nfds = max(nfds, inotify_fd);
|
|
}
|
|
if (netlink_fd >= 0) {
|
|
FD_SET(netlink_fd, &rfds);
|
|
nfds = max(nfds, netlink_fd);
|
|
}
|
|
|
|
/* If waiting fails, just fall through, presumably reading input will fail */
|
|
if (select(nfds + 1, &rfds, NULL, NULL, NULL) < 0)
|
|
return 1;
|
|
|
|
if (FD_ISSET(fd, &rfds)) {
|
|
return 1;
|
|
|
|
} else if (netlink_fd >= 0 && FD_ISSET(netlink_fd, &rfds)) {
|
|
if (!process_netlink())
|
|
continue;
|
|
|
|
/* Just drain the inotify buffer */
|
|
} else if (inotify_fd >= 0 && FD_ISSET(inotify_fd, &rfds)) {
|
|
while (read(inotify_fd, buffer, sizeof (buffer)) > 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
#endif /* AGETTY_RELOAD */
|
|
|
|
#ifdef ISSUEDIR_SUPPORT
|
|
static int issuedir_filter(const struct dirent *d)
|
|
{
|
|
size_t namesz;
|
|
|
|
#ifdef _DIRENT_HAVE_D_TYPE
|
|
if (d->d_type != DT_UNKNOWN && d->d_type != DT_REG &&
|
|
d->d_type != DT_LNK)
|
|
return 0;
|
|
#endif
|
|
if (*d->d_name == '.')
|
|
return 0;
|
|
|
|
namesz = strlen(d->d_name);
|
|
if (!namesz || namesz < ISSUEDIR_EXTSIZ + 1 ||
|
|
strcmp(d->d_name + (namesz - ISSUEDIR_EXTSIZ), ISSUEDIR_EXT))
|
|
return 0;
|
|
|
|
/* Accept this */
|
|
return 1;
|
|
}
|
|
|
|
static FILE *issuedir_next_file(int dd, struct dirent **namelist, int nfiles, int *n)
|
|
{
|
|
while (*n < nfiles) {
|
|
struct dirent *d = namelist[*n];
|
|
struct stat st;
|
|
FILE *f;
|
|
|
|
(*n)++;
|
|
|
|
if (fstatat(dd, d->d_name, &st, 0) ||
|
|
!S_ISREG(st.st_mode))
|
|
continue;
|
|
|
|
f = fopen_at(dd, d->d_name, O_RDONLY|O_CLOEXEC, "r" UL_CLOEXECSTR);
|
|
if (f)
|
|
return f;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#endif /* ISSUEDIR_SUPPORT */
|
|
|
|
#ifndef ISSUE_SUPPORT
|
|
static void print_issue_file(struct issue *ie __attribute__((__unused__)),
|
|
struct options *op,
|
|
struct termios *tp __attribute__((__unused__)))
|
|
{
|
|
if ((op->flags & F_NONL) == 0) {
|
|
/* Issue not in use, start with a new line. */
|
|
write_all(STDOUT_FILENO, "\r\n", 2);
|
|
}
|
|
}
|
|
|
|
static void eval_issue_file(struct issue *ie __attribute__((__unused__)),
|
|
struct options *op __attribute__((__unused__)),
|
|
struct termios *tp __attribute__((__unused__)))
|
|
{
|
|
}
|
|
#else /* ISSUE_SUPPORT */
|
|
|
|
#ifdef AGETTY_RELOAD
|
|
static int issue_is_changed(struct issue *ie)
|
|
{
|
|
if (ie->mem_old && ie->mem
|
|
&& strcmp(ie->mem_old, ie->mem) == 0) {
|
|
free(ie->mem_old);
|
|
ie->mem_old = ie->mem;
|
|
ie->mem = NULL;
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
static void print_issue_file(struct issue *ie,
|
|
struct options *op,
|
|
struct termios *tp)
|
|
{
|
|
int oflag = tp->c_oflag; /* Save current setting. */
|
|
|
|
if ((op->flags & F_NONL) == 0) {
|
|
/* Issue not in use, start with a new line. */
|
|
write_all(STDOUT_FILENO, "\r\n", 2);
|
|
}
|
|
|
|
if (ie->do_tcsetattr) {
|
|
if ((op->flags & F_VCONSOLE) == 0) {
|
|
/* Map new line in output to carriage return & new line. */
|
|
tp->c_oflag |= (ONLCR | OPOST);
|
|
tcsetattr(STDIN_FILENO, TCSADRAIN, tp);
|
|
}
|
|
}
|
|
|
|
if (ie->mem_sz)
|
|
write_all(STDOUT_FILENO, ie->mem, ie->mem_sz);
|
|
|
|
if (ie->do_tcrestore) {
|
|
/* Restore settings. */
|
|
tp->c_oflag = oflag;
|
|
/* Wait till output is gone. */
|
|
tcsetattr(STDIN_FILENO, TCSADRAIN, tp);
|
|
}
|
|
|
|
#ifdef AGETTY_RELOAD
|
|
free(ie->mem_old);
|
|
ie->mem_old = ie->mem;
|
|
ie->mem = NULL;
|
|
ie->mem_sz = 0;
|
|
#else
|
|
free(ie->mem);
|
|
ie->mem = NULL;
|
|
ie->mem_sz = 0;
|
|
#endif
|
|
}
|
|
|
|
static void eval_issue_file(struct issue *ie,
|
|
struct options *op,
|
|
struct termios *tp)
|
|
{
|
|
const char *filename, *dirname = NULL;
|
|
FILE *f = NULL;
|
|
#ifdef ISSUEDIR_SUPPORT
|
|
int dd = -1, nfiles = 0, i;
|
|
struct dirent **namelist = NULL;
|
|
#endif
|
|
#ifdef AGETTY_RELOAD
|
|
netlink_groups = 0;
|
|
#endif
|
|
|
|
if (!(op->flags & F_ISSUE))
|
|
return;
|
|
|
|
/*
|
|
* The custom issue file or directory specified by: agetty -f <path>.
|
|
* Note that nothing is printed if the file/dir does not exist.
|
|
*/
|
|
filename = op->issue;
|
|
if (filename) {
|
|
struct stat st;
|
|
|
|
if (stat(filename, &st) < 0)
|
|
return;
|
|
if (S_ISDIR(st.st_mode)) {
|
|
dirname = filename;
|
|
filename = NULL;
|
|
}
|
|
} else {
|
|
/* The default /etc/issue and optional /etc/issue.d directory
|
|
* as extension to the file. The /etc/issue.d directory is
|
|
* ignored if there is no /etc/issue file. The file may be
|
|
* empty or symlink.
|
|
*/
|
|
if (access(_PATH_ISSUE, F_OK|R_OK) != 0)
|
|
return;
|
|
filename = _PATH_ISSUE;
|
|
dirname = _PATH_ISSUEDIR;
|
|
}
|
|
|
|
ie->output = open_memstream(&ie->mem, &ie->mem_sz);
|
|
#ifdef ISSUEDIR_SUPPORT
|
|
if (dirname) {
|
|
dd = open(dirname, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
|
|
if (dd >= 0)
|
|
nfiles = scandirat(dd, ".", &namelist, issuedir_filter, versionsort);
|
|
if (nfiles <= 0)
|
|
dirname = NULL;
|
|
}
|
|
i = 0;
|
|
#endif
|
|
if (filename)
|
|
f = fopen(filename, "r");
|
|
|
|
if (f || dirname) {
|
|
int c;
|
|
|
|
ie->do_tcsetattr = 1;
|
|
|
|
do {
|
|
#ifdef ISSUEDIR_SUPPORT
|
|
if (!f && i < nfiles)
|
|
f = issuedir_next_file(dd, namelist, nfiles, &i);
|
|
#endif
|
|
if (!f)
|
|
break;
|
|
while ((c = getc(f)) != EOF) {
|
|
if (c == '\\')
|
|
output_special_char(ie, getc(f), op, tp, f);
|
|
else
|
|
putc(c, ie->output);
|
|
}
|
|
fclose(f);
|
|
f = NULL;
|
|
} while (dirname);
|
|
|
|
if ((op->flags & F_VCONSOLE) == 0)
|
|
ie->do_tcrestore = 1;
|
|
}
|
|
|
|
#ifdef ISSUEDIR_SUPPORT
|
|
for (i = 0; i < nfiles; i++)
|
|
free(namelist[i]);
|
|
free(namelist);
|
|
if (dd >= 0)
|
|
close(dd);
|
|
#endif
|
|
#ifdef AGETTY_RELOAD
|
|
if (netlink_groups != 0)
|
|
open_netlink();
|
|
#endif
|
|
fclose(ie->output);
|
|
}
|
|
#endif /* ISSUE_SUPPORT */
|
|
|
|
/* Show login prompt, optionally preceded by /etc/issue contents. */
|
|
static void do_prompt(struct issue *ie, struct options *op, struct termios *tp)
|
|
{
|
|
#ifdef AGETTY_RELOAD
|
|
again:
|
|
#endif
|
|
print_issue_file(ie, op, tp);
|
|
|
|
if (op->flags & F_LOGINPAUSE) {
|
|
puts(_("[press ENTER to login]"));
|
|
#ifdef AGETTY_RELOAD
|
|
/* reload issue */
|
|
if (!wait_for_term_input(STDIN_FILENO)) {
|
|
eval_issue_file(ie, op, tp);
|
|
if (issue_is_changed(ie)) {
|
|
if (op->flags & F_VCONSOLE)
|
|
termio_clear(STDOUT_FILENO);
|
|
goto again;
|
|
}
|
|
}
|
|
#endif
|
|
getc(stdin);
|
|
}
|
|
#ifdef KDGKBLED
|
|
if (!(op->flags & F_NOHINTS) && !op->autolog &&
|
|
(op->flags & F_VCONSOLE)) {
|
|
int kb = 0;
|
|
|
|
if (ioctl(STDIN_FILENO, KDGKBLED, &kb) == 0) {
|
|
char hint[256] = { '\0' };
|
|
int nl = 0;
|
|
|
|
if (access(_PATH_NUMLOCK_ON, F_OK) == 0)
|
|
nl = 1;
|
|
|
|
if (nl && (kb & 0x02) == 0)
|
|
append(hint, sizeof(hint), NULL, _("Num Lock off"));
|
|
|
|
else if (nl == 0 && (kb & 2) && (kb & 0x20) == 0)
|
|
append(hint, sizeof(hint), NULL, _("Num Lock on"));
|
|
|
|
if ((kb & 0x04) && (kb & 0x40) == 0)
|
|
append(hint, sizeof(hint), ", ", _("Caps Lock on"));
|
|
|
|
if ((kb & 0x01) && (kb & 0x10) == 0)
|
|
append(hint, sizeof(hint), ", ", _("Scroll Lock on"));
|
|
|
|
if (*hint)
|
|
printf(_("Hint: %s\n\n"), hint);
|
|
}
|
|
}
|
|
#endif /* KDGKBLED */
|
|
if ((op->flags & F_NOHOSTNAME) == 0) {
|
|
char *hn = xgethostname();
|
|
|
|
if (hn) {
|
|
char *dot = strchr(hn, '.');
|
|
char *cn = hn;
|
|
struct addrinfo *res = NULL;
|
|
|
|
if ((op->flags & F_LONGHNAME) == 0) {
|
|
if (dot)
|
|
*dot = '\0';
|
|
|
|
} else if (dot == NULL) {
|
|
struct addrinfo hints;
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_flags = AI_CANONNAME;
|
|
|
|
if (!getaddrinfo(hn, NULL, &hints, &res)
|
|
&& res && res->ai_canonname)
|
|
cn = res->ai_canonname;
|
|
}
|
|
|
|
write_all(STDOUT_FILENO, cn, strlen(cn));
|
|
write_all(STDOUT_FILENO, " ", 1);
|
|
|
|
if (res)
|
|
freeaddrinfo(res);
|
|
free(hn);
|
|
}
|
|
}
|
|
if (!op->autolog) {
|
|
/* Always show login prompt. */
|
|
write_all(STDOUT_FILENO, LOGIN, sizeof(LOGIN) - 1);
|
|
}
|
|
}
|
|
|
|
/* Select next baud rate. */
|
|
static void next_speed(struct options *op, struct termios *tp)
|
|
{
|
|
static int baud_index = -1;
|
|
|
|
if (baud_index == -1)
|
|
/*
|
|
* If the F_KEEPSPEED flags is set then the FIRST_SPEED is not
|
|
* tested yet (see termio_init()).
|
|
*/
|
|
baud_index =
|
|
(op->flags & F_KEEPSPEED) ? FIRST_SPEED : 1 % op->numspeed;
|
|
else
|
|
baud_index = (baud_index + 1) % op->numspeed;
|
|
|
|
cfsetispeed(tp, op->speeds[baud_index]);
|
|
cfsetospeed(tp, op->speeds[baud_index]);
|
|
tcsetattr(STDIN_FILENO, TCSANOW, tp);
|
|
}
|
|
|
|
/* Get user name, establish parity, speed, erase, kill & eol. */
|
|
static char *get_logname(struct issue *ie, struct options *op, struct termios *tp, struct chardata *cp)
|
|
{
|
|
static char logname[BUFSIZ];
|
|
char *bp;
|
|
char c; /* input character, full eight bits */
|
|
char ascval; /* low 7 bits of input character */
|
|
int eightbit;
|
|
static char *erase[] = { /* backspace-space-backspace */
|
|
"\010\040\010", /* space parity */
|
|
"\010\040\010", /* odd parity */
|
|
"\210\240\210", /* even parity */
|
|
"\210\240\210", /* no parity */
|
|
};
|
|
|
|
/* Initialize kill, erase, parity etc. (also after switching speeds). */
|
|
INIT_CHARDATA(cp);
|
|
|
|
/*
|
|
* Flush pending input (especially important after parsing or switching
|
|
* the baud rate).
|
|
*/
|
|
if ((op->flags & F_VCONSOLE) == 0)
|
|
sleep(1);
|
|
tcflush(STDIN_FILENO, TCIFLUSH);
|
|
|
|
eightbit = (op->flags & (F_EIGHTBITS|F_UTF8));
|
|
bp = logname;
|
|
*bp = '\0';
|
|
|
|
eval_issue_file(ie, op, tp);
|
|
while (*logname == '\0') {
|
|
/* Write issue file and prompt */
|
|
do_prompt(ie, op, tp);
|
|
|
|
no_reload:
|
|
#ifdef AGETTY_RELOAD
|
|
if (!wait_for_term_input(STDIN_FILENO)) {
|
|
/* refresh prompt -- discard input data, clear terminal
|
|
* and call do_prompt() again
|
|
*/
|
|
if ((op->flags & F_VCONSOLE) == 0)
|
|
sleep(1);
|
|
eval_issue_file(ie, op, tp);
|
|
if (!issue_is_changed(ie))
|
|
goto no_reload;
|
|
tcflush(STDIN_FILENO, TCIFLUSH);
|
|
if (op->flags & F_VCONSOLE)
|
|
termio_clear(STDOUT_FILENO);
|
|
bp = logname;
|
|
*bp = '\0';
|
|
continue;
|
|
}
|
|
#endif
|
|
cp->eol = '\0';
|
|
|
|
/* Read name, watch for break and end-of-line. */
|
|
while (cp->eol == '\0') {
|
|
|
|
char key;
|
|
ssize_t readres;
|
|
|
|
debug("read from FD\n");
|
|
readres = read(STDIN_FILENO, &c, 1);
|
|
if (readres < 0) {
|
|
debug("read failed\n");
|
|
|
|
/* The terminal could be open with O_NONBLOCK when
|
|
* -L (force CLOCAL) is specified... */
|
|
if (errno == EINTR || errno == EAGAIN) {
|
|
xusleep(250000);
|
|
continue;
|
|
}
|
|
switch (errno) {
|
|
case 0:
|
|
case EIO:
|
|
case ESRCH:
|
|
case EINVAL:
|
|
case ENOENT:
|
|
exit_slowly(EXIT_SUCCESS);
|
|
default:
|
|
log_err(_("%s: read: %m"), op->tty);
|
|
}
|
|
}
|
|
|
|
if (readres == 0)
|
|
c = 0;
|
|
|
|
/* Do parity bit handling. */
|
|
if (eightbit)
|
|
ascval = c;
|
|
else if (c != (ascval = (c & 0177))) {
|
|
uint32_t bits; /* # of "1" bits per character */
|
|
uint32_t mask; /* mask with 1 bit up */
|
|
for (bits = 1, mask = 1; mask & 0177; mask <<= 1) {
|
|
if (mask & ascval)
|
|
bits++;
|
|
}
|
|
cp->parity |= ((bits & 1) ? 1 : 2);
|
|
}
|
|
|
|
if (op->killchars && strchr(op->killchars, ascval))
|
|
key = CTL('U');
|
|
else if (op->erasechars && strchr(op->erasechars, ascval))
|
|
key = DEL;
|
|
else
|
|
key = ascval;
|
|
|
|
/* Do erase, kill and end-of-line processing. */
|
|
switch (key) {
|
|
case 0:
|
|
*bp = 0;
|
|
if (op->numspeed > 1 && !(op->flags & F_VCONSOLE))
|
|
return NULL;
|
|
if (readres == 0)
|
|
exit_slowly(EXIT_SUCCESS);
|
|
break;
|
|
case CR:
|
|
case NL:
|
|
*bp = 0; /* terminate logname */
|
|
cp->eol = ascval; /* set end-of-line char */
|
|
break;
|
|
case BS:
|
|
case DEL:
|
|
cp->erase = ascval; /* set erase character */
|
|
if (bp > logname) {
|
|
if ((tp->c_lflag & ECHO) == 0)
|
|
write_all(1, erase[cp->parity], 3);
|
|
bp--;
|
|
}
|
|
break;
|
|
case CTL('U'):
|
|
cp->kill = ascval; /* set kill character */
|
|
while (bp > logname) {
|
|
if ((tp->c_lflag & ECHO) == 0)
|
|
write_all(1, erase[cp->parity], 3);
|
|
bp--;
|
|
}
|
|
break;
|
|
case CTL('D'):
|
|
exit(EXIT_SUCCESS);
|
|
default:
|
|
if ((size_t)(bp - logname) >= sizeof(logname) - 1)
|
|
log_err(_("%s: input overrun"), op->tty);
|
|
if ((tp->c_lflag & ECHO) == 0)
|
|
write_all(1, &c, 1); /* echo the character */
|
|
*bp++ = ascval; /* and store it */
|
|
break;
|
|
}
|
|
/* Everything was erased. */
|
|
if (bp == logname && cp->eol == '\0')
|
|
goto no_reload;
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_WIDECHAR
|
|
if ((op->flags & (F_EIGHTBITS|F_UTF8)) == (F_EIGHTBITS|F_UTF8)) {
|
|
/* Check out UTF-8 multibyte characters */
|
|
ssize_t len;
|
|
wchar_t *wcs, *wcp;
|
|
|
|
len = mbstowcs((wchar_t *)0, logname, 0);
|
|
if (len < 0)
|
|
log_err(_("%s: invalid character conversion for login name"), op->tty);
|
|
|
|
wcs = malloc((len + 1) * sizeof(wchar_t));
|
|
if (!wcs)
|
|
log_err(_("failed to allocate memory: %m"));
|
|
|
|
len = mbstowcs(wcs, logname, len + 1);
|
|
if (len < 0)
|
|
log_err(_("%s: invalid character conversion for login name"), op->tty);
|
|
|
|
wcp = wcs;
|
|
while (*wcp) {
|
|
const wint_t wc = *wcp++;
|
|
if (!iswprint(wc))
|
|
log_err(_("%s: invalid character 0x%x in login name"), op->tty, wc);
|
|
}
|
|
free(wcs);
|
|
} else
|
|
#endif
|
|
if ((op->flags & F_LCUC) && (cp->capslock = caps_lock(logname))) {
|
|
|
|
/* Handle names with upper case and no lower case. */
|
|
for (bp = logname; *bp; bp++)
|
|
if (isupper(*bp))
|
|
*bp = tolower(*bp); /* map name to lower case */
|
|
}
|
|
|
|
return logname;
|
|
}
|
|
|
|
/* Set the final tty mode bits. */
|
|
static void termio_final(struct options *op, struct termios *tp, struct chardata *cp)
|
|
{
|
|
/* General terminal-independent stuff. */
|
|
|
|
/* 2-way flow control */
|
|
tp->c_iflag |= IXON | IXOFF;
|
|
tp->c_lflag |= ICANON | ISIG | ECHO | ECHOE | ECHOK | ECHOKE;
|
|
/* no longer| ECHOCTL | ECHOPRT */
|
|
tp->c_oflag |= OPOST;
|
|
/* tp->c_cflag = 0; */
|
|
tp->c_cc[VINTR] = DEF_INTR;
|
|
tp->c_cc[VQUIT] = DEF_QUIT;
|
|
tp->c_cc[VEOF] = DEF_EOF;
|
|
tp->c_cc[VEOL] = DEF_EOL;
|
|
#ifdef __linux__
|
|
tp->c_cc[VSWTC] = DEF_SWITCH;
|
|
#elif defined(VSWTCH)
|
|
tp->c_cc[VSWTCH] = DEF_SWITCH;
|
|
#endif /* __linux__ */
|
|
|
|
/* Account for special characters seen in input. */
|
|
if (cp->eol == CR) {
|
|
tp->c_iflag |= ICRNL;
|
|
tp->c_oflag |= ONLCR;
|
|
}
|
|
tp->c_cc[VERASE] = cp->erase;
|
|
tp->c_cc[VKILL] = cp->kill;
|
|
|
|
/* Account for the presence or absence of parity bits in input. */
|
|
switch (cp->parity) {
|
|
case 0:
|
|
/* space (always 0) parity */
|
|
break;
|
|
case 1:
|
|
/* odd parity */
|
|
tp->c_cflag |= PARODD;
|
|
/* fallthrough */
|
|
case 2:
|
|
/* even parity */
|
|
tp->c_cflag |= PARENB;
|
|
tp->c_iflag |= INPCK | ISTRIP;
|
|
/* fallthrough */
|
|
case (1 | 2):
|
|
/* no parity bit */
|
|
tp->c_cflag &= ~CSIZE;
|
|
tp->c_cflag |= CS7;
|
|
break;
|
|
}
|
|
/* Account for upper case without lower case. */
|
|
if (cp->capslock) {
|
|
#ifdef IUCLC
|
|
tp->c_iflag |= IUCLC;
|
|
#endif
|
|
#ifdef XCASE
|
|
tp->c_lflag |= XCASE;
|
|
#endif
|
|
#ifdef OLCUC
|
|
tp->c_oflag |= OLCUC;
|
|
#endif
|
|
}
|
|
/* Optionally enable hardware flow control. */
|
|
#ifdef CRTSCTS
|
|
if (op->flags & F_RTSCTS)
|
|
tp->c_cflag |= CRTSCTS;
|
|
#endif
|
|
|
|
/* Finally, make the new settings effective. */
|
|
if (tcsetattr(STDIN_FILENO, TCSANOW, tp) < 0)
|
|
log_err(_("%s: failed to set terminal attributes: %m"), op->tty);
|
|
}
|
|
|
|
/*
|
|
* String contains upper case without lower case.
|
|
* http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=52940
|
|
* http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=156242
|
|
*/
|
|
static int caps_lock(char *s)
|
|
{
|
|
int capslock;
|
|
|
|
for (capslock = 0; *s; s++) {
|
|
if (islower(*s))
|
|
return EXIT_SUCCESS;
|
|
if (capslock == 0)
|
|
capslock = isupper(*s);
|
|
}
|
|
return capslock;
|
|
}
|
|
|
|
/* Convert speed string to speed code; return 0 on failure. */
|
|
static speed_t bcode(char *s)
|
|
{
|
|
const struct Speedtab *sp;
|
|
long speed = atol(s);
|
|
|
|
for (sp = speedtab; sp->speed; sp++)
|
|
if (sp->speed == speed)
|
|
return sp->code;
|
|
return 0;
|
|
}
|
|
|
|
static void __attribute__((__noreturn__)) usage(void)
|
|
{
|
|
FILE *out = stdout;
|
|
|
|
fputs(USAGE_HEADER, out);
|
|
fprintf(out, _(" %1$s [options] <line> [<baud_rate>,...] [<termtype>]\n"
|
|
" %1$s [options] <baud_rate>,... <line> [<termtype>]\n"), program_invocation_short_name);
|
|
|
|
fputs(USAGE_SEPARATOR, out);
|
|
fputs(_("Open a terminal and set its mode.\n"), out);
|
|
|
|
fputs(USAGE_OPTIONS, out);
|
|
fputs(_(" -8, --8bits assume 8-bit tty\n"), out);
|
|
fputs(_(" -a, --autologin <user> login the specified user automatically\n"), out);
|
|
fputs(_(" -c, --noreset do not reset control mode\n"), out);
|
|
fputs(_(" -E, --remote use -r <hostname> for login(1)\n"), out);
|
|
fputs(_(" -f, --issue-file <file> display issue file\n"), out);
|
|
fputs(_(" -h, --flow-control enable hardware flow control\n"), out);
|
|
fputs(_(" -H, --host <hostname> specify login host\n"), out);
|
|
fputs(_(" -i, --noissue do not display issue file\n"), out);
|
|
fputs(_(" -I, --init-string <string> set init string\n"), out);
|
|
fputs(_(" -J --noclear do not clear the screen before prompt\n"), out);
|
|
fputs(_(" -l, --login-program <file> specify login program\n"), out);
|
|
fputs(_(" -L, --local-line[=<mode>] control the local line flag\n"), out);
|
|
fputs(_(" -m, --extract-baud extract baud rate during connect\n"), out);
|
|
fputs(_(" -n, --skip-login do not prompt for login\n"), out);
|
|
fputs(_(" -N --nonewline do not print a newline before issue\n"), out);
|
|
fputs(_(" -o, --login-options <opts> options that are passed to login\n"), out);
|
|
fputs(_(" -p, --login-pause wait for any key before the login\n"), out);
|
|
fputs(_(" -r, --chroot <dir> change root to the directory\n"), out);
|
|
fputs(_(" -R, --hangup do virtually hangup on the tty\n"), out);
|
|
fputs(_(" -s, --keep-baud try to keep baud rate after break\n"), out);
|
|
fputs(_(" -t, --timeout <number> login process timeout\n"), out);
|
|
fputs(_(" -U, --detect-case detect uppercase terminal\n"), out);
|
|
fputs(_(" -w, --wait-cr wait carriage-return\n"), out);
|
|
fputs(_(" --nohints do not print hints\n"), out);
|
|
fputs(_(" --nohostname no hostname at all will be shown\n"), out);
|
|
fputs(_(" --long-hostname show full qualified hostname\n"), out);
|
|
fputs(_(" --erase-chars <string> additional backspace chars\n"), out);
|
|
fputs(_(" --kill-chars <string> additional kill chars\n"), out);
|
|
fputs(_(" --chdir <directory> chdir before the login\n"), out);
|
|
fputs(_(" --delay <number> sleep seconds before prompt\n"), out);
|
|
fputs(_(" --nice <number> run login with this priority\n"), out);
|
|
fputs(_(" --reload reload prompts on running agetty instances\n"), out);
|
|
fputs(_(" --list-speeds display supported baud rates\n"), out);
|
|
printf( " --help %s\n", USAGE_OPTSTR_HELP);
|
|
printf( " --version %s\n", USAGE_OPTSTR_VERSION);
|
|
printf(USAGE_MAN_TAIL("agetty(8)"));
|
|
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
static void list_speeds(void)
|
|
{
|
|
const struct Speedtab *sp;
|
|
|
|
for (sp = speedtab; sp->speed; sp++)
|
|
printf("%10ld\n", sp->speed);
|
|
}
|
|
|
|
/*
|
|
* Helper function reports errors to console or syslog.
|
|
* Will be used by log_err() and log_warn() therefore
|
|
* it takes a format as well as va_list.
|
|
*/
|
|
static void dolog(int priority
|
|
#ifndef USE_SYSLOG
|
|
__attribute__((__unused__))
|
|
#endif
|
|
, const char *fmt, va_list ap)
|
|
{
|
|
#ifdef USE_SYSLOG
|
|
/*
|
|
* If the diagnostic is reported via syslog(3), the process name is
|
|
* automatically prepended to the message. If we write directly to
|
|
* /dev/console, we must prepend the process name ourselves.
|
|
*/
|
|
openlog(program_invocation_short_name, LOG_PID, LOG_AUTHPRIV);
|
|
vsyslog(priority, fmt, ap);
|
|
closelog();
|
|
#else
|
|
/*
|
|
* Write the diagnostic directly to /dev/console if we do not use
|
|
* the syslog(3) facility.
|
|
*/
|
|
char buf[BUFSIZ];
|
|
char new_fmt[BUFSIZ];
|
|
int fd;
|
|
|
|
snprintf(new_fmt, sizeof(new_fmt), "%s: %s\r\n",
|
|
program_invocation_short_name, fmt);
|
|
/* Terminate with CR-LF since the console mode is unknown. */
|
|
vsnprintf(buf, sizeof(buf), new_fmt, ap);
|
|
|
|
if ((fd = open("/dev/console", 1)) >= 0) {
|
|
write_all(fd, buf, strlen(buf));
|
|
close(fd);
|
|
}
|
|
#endif /* USE_SYSLOG */
|
|
}
|
|
|
|
static void exit_slowly(int code)
|
|
{
|
|
/* Be kind to init(8). */
|
|
sleep(10);
|
|
exit(code);
|
|
}
|
|
|
|
static void log_err(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
dolog(LOG_ERR, fmt, ap);
|
|
va_end(ap);
|
|
|
|
exit_slowly(EXIT_FAILURE);
|
|
}
|
|
|
|
static void log_warn(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
dolog(LOG_WARNING, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
static void print_addr(struct issue *ie, sa_family_t family, void *addr)
|
|
{
|
|
char buff[INET6_ADDRSTRLEN + 1];
|
|
|
|
inet_ntop(family, addr, buff, sizeof(buff));
|
|
fprintf(ie->output, "%s", buff);
|
|
}
|
|
|
|
/*
|
|
* Prints IP for the specified interface (@iface), if the interface is not
|
|
* specified then prints the "best" one (UP, RUNNING, non-LOOPBACK). If not
|
|
* found the "best" interface then prints at least host IP.
|
|
*/
|
|
static void output_iface_ip(struct issue *ie,
|
|
struct ifaddrs *addrs,
|
|
const char *iface,
|
|
sa_family_t family)
|
|
{
|
|
struct ifaddrs *p;
|
|
struct addrinfo hints, *info = NULL;
|
|
char *host = NULL;
|
|
void *addr = NULL;
|
|
|
|
if (!addrs)
|
|
return;
|
|
|
|
for (p = addrs; p; p = p->ifa_next) {
|
|
|
|
if (!p->ifa_name ||
|
|
!p->ifa_addr ||
|
|
p->ifa_addr->sa_family != family)
|
|
continue;
|
|
|
|
if (iface) {
|
|
/* Filter out by interface name */
|
|
if (strcmp(p->ifa_name, iface) != 0)
|
|
continue;
|
|
} else {
|
|
/* Select the "best" interface */
|
|
if ((p->ifa_flags & IFF_LOOPBACK) ||
|
|
!(p->ifa_flags & IFF_UP) ||
|
|
!(p->ifa_flags & IFF_RUNNING))
|
|
continue;
|
|
}
|
|
|
|
addr = NULL;
|
|
switch (p->ifa_addr->sa_family) {
|
|
case AF_INET:
|
|
addr = &((struct sockaddr_in *) p->ifa_addr)->sin_addr;
|
|
break;
|
|
case AF_INET6:
|
|
addr = &((struct sockaddr_in6 *) p->ifa_addr)->sin6_addr;
|
|
break;
|
|
}
|
|
|
|
if (addr) {
|
|
print_addr(ie, family, addr);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (iface)
|
|
return;
|
|
|
|
/* Hmm.. not found the best interface, print host IP at least */
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = family;
|
|
if (family == AF_INET6)
|
|
hints.ai_flags = AI_V4MAPPED;
|
|
|
|
host = xgethostname();
|
|
if (host && getaddrinfo(host, NULL, &hints, &info) == 0 && info) {
|
|
switch (info->ai_family) {
|
|
case AF_INET:
|
|
addr = &((struct sockaddr_in *) info->ai_addr)->sin_addr;
|
|
break;
|
|
case AF_INET6:
|
|
addr = &((struct sockaddr_in6 *) info->ai_addr)->sin6_addr;
|
|
break;
|
|
}
|
|
if (addr)
|
|
print_addr(ie, family, addr);
|
|
|
|
freeaddrinfo(info);
|
|
}
|
|
free(host);
|
|
}
|
|
|
|
/*
|
|
* parses \x{argument}, if not argument specified then returns NULL, the @fd
|
|
* has to point to one char after the sequence (it means '{').
|
|
*/
|
|
static char *get_escape_argument(FILE *fd, char *buf, size_t bufsz)
|
|
{
|
|
size_t i = 0;
|
|
int c = fgetc(fd);
|
|
|
|
if (c == EOF || (unsigned char) c != '{') {
|
|
ungetc(c, fd);
|
|
return NULL;
|
|
}
|
|
|
|
do {
|
|
c = fgetc(fd);
|
|
if (c == EOF)
|
|
return NULL;
|
|
if ((unsigned char) c != '}' && i < bufsz - 1)
|
|
buf[i++] = (unsigned char) c;
|
|
|
|
} while ((unsigned char) c != '}');
|
|
|
|
buf[i] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
static void output_special_char(struct issue *ie,
|
|
unsigned char c,
|
|
struct options *op,
|
|
struct termios *tp,
|
|
FILE *fp)
|
|
{
|
|
struct utsname uts;
|
|
|
|
switch (c) {
|
|
case 'e':
|
|
{
|
|
char escname[UL_COLORNAME_MAXSZ];
|
|
|
|
if (get_escape_argument(fp, escname, sizeof(escname))) {
|
|
const char *esc = color_sequence_from_colorname(escname);
|
|
if (esc)
|
|
fputs(esc, ie->output);
|
|
} else
|
|
fputs("\033", ie->output);
|
|
break;
|
|
}
|
|
case 's':
|
|
uname(&uts);
|
|
fprintf(ie->output, "%s", uts.sysname);
|
|
break;
|
|
case 'n':
|
|
uname(&uts);
|
|
fprintf(ie->output, "%s", uts.nodename);
|
|
break;
|
|
case 'r':
|
|
uname(&uts);
|
|
fprintf(ie->output, "%s", uts.release);
|
|
break;
|
|
case 'v':
|
|
uname(&uts);
|
|
fprintf(ie->output, "%s", uts.version);
|
|
break;
|
|
case 'm':
|
|
uname(&uts);
|
|
fprintf(ie->output, "%s", uts.machine);
|
|
break;
|
|
case 'o':
|
|
{
|
|
char *dom = xgetdomainname();
|
|
|
|
fputs(dom ? dom : "unknown_domain", ie->output);
|
|
free(dom);
|
|
break;
|
|
}
|
|
case 'O':
|
|
{
|
|
char *dom = NULL;
|
|
char *host = xgethostname();
|
|
struct addrinfo hints, *info = NULL;
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_flags = AI_CANONNAME;
|
|
|
|
if (host && getaddrinfo(host, NULL, &hints, &info) == 0 && info) {
|
|
char *canon;
|
|
|
|
if (info->ai_canonname &&
|
|
(canon = strchr(info->ai_canonname, '.')))
|
|
dom = canon + 1;
|
|
}
|
|
fputs(dom ? dom : "unknown_domain", ie->output);
|
|
if (info)
|
|
freeaddrinfo(info);
|
|
free(host);
|
|
break;
|
|
}
|
|
case 'd':
|
|
case 't':
|
|
{
|
|
time_t now;
|
|
struct tm *tm;
|
|
|
|
time(&now);
|
|
tm = localtime(&now);
|
|
|
|
if (!tm)
|
|
break;
|
|
|
|
if (c == 'd') /* ISO 8601 */
|
|
fprintf(ie->output, "%s %s %d %d",
|
|
nl_langinfo(ABDAY_1 + tm->tm_wday),
|
|
nl_langinfo(ABMON_1 + tm->tm_mon),
|
|
tm->tm_mday,
|
|
tm->tm_year < 70 ? tm->tm_year + 2000 :
|
|
tm->tm_year + 1900);
|
|
else
|
|
fprintf(ie->output, "%02d:%02d:%02d",
|
|
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
|
break;
|
|
}
|
|
case 'l':
|
|
fprintf (ie->output, "%s", op->tty);
|
|
break;
|
|
case 'b':
|
|
{
|
|
const speed_t speed = cfgetispeed(tp);
|
|
int i;
|
|
|
|
for (i = 0; speedtab[i].speed; i++) {
|
|
if (speedtab[i].code == speed) {
|
|
fprintf(ie->output, "%ld", speedtab[i].speed);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 'S':
|
|
{
|
|
char *var = NULL, varname[64];
|
|
|
|
/* \S{varname} */
|
|
if (get_escape_argument(fp, varname, sizeof(varname))) {
|
|
var = read_os_release(op, varname);
|
|
if (var) {
|
|
if (strcmp(varname, "ANSI_COLOR") == 0)
|
|
fprintf(ie->output, "\033[%sm", var);
|
|
else
|
|
fputs(var, ie->output);
|
|
}
|
|
/* \S */
|
|
} else if ((var = read_os_release(op, "PRETTY_NAME"))) {
|
|
fputs(var, ie->output);
|
|
|
|
/* \S and PRETTY_NAME not found */
|
|
} else {
|
|
uname(&uts);
|
|
fputs(uts.sysname, ie->output);
|
|
}
|
|
|
|
free(var);
|
|
|
|
break;
|
|
}
|
|
case 'u':
|
|
case 'U':
|
|
{
|
|
int users = 0;
|
|
struct utmpx *ut;
|
|
setutxent();
|
|
while ((ut = getutxent()))
|
|
if (ut->ut_type == USER_PROCESS)
|
|
users++;
|
|
endutxent();
|
|
if (c == 'U')
|
|
fprintf(ie->output, P_("%d user", "%d users", users), users);
|
|
else
|
|
fprintf (ie->output, "%d ", users);
|
|
break;
|
|
}
|
|
#if defined(RTMGRP_IPV4_IFADDR) && defined(RTMGRP_IPV6_IFADDR)
|
|
case '4':
|
|
case '6':
|
|
{
|
|
sa_family_t family = c == '4' ? AF_INET : AF_INET6;
|
|
struct ifaddrs *addrs = NULL;
|
|
char iface[128];
|
|
|
|
if (getifaddrs(&addrs))
|
|
break;
|
|
|
|
if (get_escape_argument(fp, iface, sizeof(iface)))
|
|
output_iface_ip(ie, addrs, iface, family);
|
|
else
|
|
output_iface_ip(ie, addrs, NULL, family);
|
|
|
|
freeifaddrs(addrs);
|
|
|
|
if (c == '4')
|
|
netlink_groups |= RTMGRP_IPV4_IFADDR;
|
|
else
|
|
netlink_groups |= RTMGRP_IPV6_IFADDR;
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
putc(c, ie->output);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void init_special_char(char* arg, struct options *op)
|
|
{
|
|
char ch, *p, *q;
|
|
int i;
|
|
|
|
op->initstring = malloc(strlen(arg) + 1);
|
|
if (!op->initstring)
|
|
log_err(_("failed to allocate memory: %m"));
|
|
|
|
/*
|
|
* Copy optarg into op->initstring decoding \ddd octal
|
|
* codes into chars.
|
|
*/
|
|
q = op->initstring;
|
|
p = arg;
|
|
while (*p) {
|
|
/* The \\ is converted to \ */
|
|
if (*p == '\\') {
|
|
p++;
|
|
if (*p == '\\') {
|
|
ch = '\\';
|
|
p++;
|
|
} else {
|
|
/* Handle \000 - \177. */
|
|
ch = 0;
|
|
for (i = 1; i <= 3; i++) {
|
|
if (*p >= '0' && *p <= '7') {
|
|
ch <<= 3;
|
|
ch += *p - '0';
|
|
p++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
*q++ = ch;
|
|
} else
|
|
*q++ = *p++;
|
|
}
|
|
*q = '\0';
|
|
}
|
|
|
|
/*
|
|
* Appends @str to @dest and if @dest is not empty then use @sep as a
|
|
* separator. The maximal final length of the @dest is @len.
|
|
*
|
|
* Returns the final @dest length or -1 in case of error.
|
|
*/
|
|
static ssize_t append(char *dest, size_t len, const char *sep, const char *src)
|
|
{
|
|
size_t dsz = 0, ssz = 0, sz;
|
|
char *p;
|
|
|
|
if (!dest || !len || !src)
|
|
return -1;
|
|
|
|
if (*dest)
|
|
dsz = strlen(dest);
|
|
if (dsz && sep)
|
|
ssz = strlen(sep);
|
|
sz = strlen(src);
|
|
|
|
if (dsz + ssz + sz + 1 > len)
|
|
return -1;
|
|
|
|
p = dest + dsz;
|
|
if (ssz) {
|
|
memcpy(p, sep, ssz);
|
|
p += ssz;
|
|
}
|
|
memcpy(p, src, sz);
|
|
*(p + sz) = '\0';
|
|
|
|
return dsz + ssz + sz;
|
|
}
|
|
|
|
/*
|
|
* Do not allow the user to pass an option as a user name
|
|
* To be more safe: Use `--' to make sure the rest is
|
|
* interpreted as non-options by the program, if it supports it.
|
|
*/
|
|
static void check_username(const char* nm)
|
|
{
|
|
const char *p = nm;
|
|
if (!nm)
|
|
goto err;
|
|
if (strlen(nm) > 42)
|
|
goto err;
|
|
while (isspace(*p))
|
|
p++;
|
|
if (*p == '-')
|
|
goto err;
|
|
return;
|
|
err:
|
|
errno = EPERM;
|
|
log_err(_("checkname failed: %m"));
|
|
}
|
|
|
|
static void reload_agettys(void)
|
|
{
|
|
#ifdef AGETTY_RELOAD
|
|
int fd = open(AGETTY_RELOAD_FILENAME, O_CREAT|O_CLOEXEC|O_WRONLY,
|
|
S_IRUSR|S_IWUSR);
|
|
if (fd < 0)
|
|
err(EXIT_FAILURE, _("cannot open %s"), AGETTY_RELOAD_FILENAME);
|
|
|
|
if (futimens(fd, NULL) < 0 || close(fd) < 0)
|
|
err(EXIT_FAILURE, _("cannot touch file %s"),
|
|
AGETTY_RELOAD_FILENAME);
|
|
#else
|
|
/* very unusual */
|
|
errx(EXIT_FAILURE, _("--reload is unsupported on your system"));
|
|
#endif
|
|
}
|