You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
561 lines
13 KiB
561 lines
13 KiB
/* |
|
* main.c |
|
* |
|
* Copyright (C) 2005-2014 Mikael Berthe <mikael@lilotux.net> |
|
* |
|
* This program is free software; you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation; either version 2 of the License, or (at |
|
* your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, but |
|
* WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
* General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License |
|
* along with this program; if not, see <http://www.gnu.org/licenses/>. |
|
*/ |
|
|
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <unistd.h> |
|
#include <string.h> |
|
#include <signal.h> |
|
#include <termios.h> |
|
#include <sys/types.h> |
|
#include <sys/wait.h> |
|
#include <glib.h> |
|
#include <poll.h> |
|
#include <errno.h> |
|
|
|
#include "caps.h" |
|
#include "screen.h" |
|
#include "settings.h" |
|
#include "roster.h" |
|
#include "commands.h" |
|
#include "histolog.h" |
|
#include "hooks.h" |
|
#include "utils.h" |
|
#include "pgp.h" |
|
#include "otr.h" |
|
#include "xmpp.h" |
|
#include "help.h" |
|
#include "events.h" |
|
#include "compl.h" |
|
|
|
#include "fifo.h" |
|
|
|
#ifndef WAIT_ANY |
|
# define WAIT_ANY -1 |
|
#endif |
|
|
|
#ifdef USE_SIGWINCH |
|
void sigwinch_resize(void); |
|
static bool sigwinch; |
|
#endif |
|
|
|
static bool terminate_ui; |
|
GMainContext *main_context; |
|
|
|
static struct termios *backup_termios; |
|
|
|
char *mcabber_version(void) |
|
{ |
|
char *ver; |
|
ver = g_strdup(MCABBER_VERSION); |
|
return ver; |
|
} |
|
|
|
static void mcabber_terminate(const char *msg) |
|
{ |
|
fifo_deinit(); |
|
xmpp_disconnect(); |
|
scr_terminate_curses(); |
|
|
|
// Restore term settings, if needed. |
|
if (backup_termios) |
|
tcsetattr(fileno(stdin), TCSAFLUSH, backup_termios); |
|
|
|
if (msg) |
|
fprintf(stderr, "%s\n", msg); |
|
printf("Bye!\n"); |
|
exit(EXIT_SUCCESS); |
|
} |
|
|
|
void sig_handler(int signum) |
|
{ |
|
if (signum == SIGCHLD) { |
|
int status; |
|
pid_t pid; |
|
do { |
|
pid = waitpid (WAIT_ANY, &status, WNOHANG); |
|
// Check the exit status value if 'eventcmd_checkstatus' is set |
|
if (settings_opt_get_int("eventcmd_checkstatus")) { |
|
if (pid > 0) { |
|
// exit status 2 -> beep |
|
if (WIFEXITED(status) && WEXITSTATUS(status) == 2) { |
|
scr_beep(); |
|
} |
|
} |
|
} |
|
} while (pid > 0); |
|
signal(SIGCHLD, sig_handler); |
|
} else if (signum == SIGTERM) { |
|
mcabber_terminate("Killed by SIGTERM"); |
|
} else if (signum == SIGINT) { |
|
mcabber_terminate("Killed by SIGINT"); |
|
} else if (signum == SIGHUP) { |
|
mcabber_terminate("Killed by SIGHUP"); |
|
#ifdef USE_SIGWINCH |
|
} else if (signum == SIGWINCH) { |
|
sigwinch = TRUE; |
|
#endif |
|
} else { |
|
scr_LogPrint(LPRINT_LOGNORM, "Caught signal: %d", signum); |
|
} |
|
} |
|
|
|
// ask_password(what) |
|
// Return the password, or NULL. |
|
// The string must be freed after use. |
|
static char *ask_password(const char *what) |
|
{ |
|
char *password, *p; |
|
size_t passsize = 128; |
|
struct termios orig, new; |
|
|
|
password = g_new0(char, passsize); |
|
|
|
/* Turn echoing off and fail if we can't. */ |
|
if (tcgetattr(fileno(stdin), &orig) != 0) return NULL; |
|
backup_termios = &orig; |
|
|
|
new = orig; |
|
new.c_lflag &= ~ECHO; |
|
if (tcsetattr(fileno(stdin), TCSAFLUSH, &new) != 0) return NULL; |
|
|
|
/* Read the password. */ |
|
printf("Please enter %s: ", what); |
|
if (fgets(password, passsize, stdin) == NULL) return NULL; |
|
|
|
/* Restore terminal. */ |
|
tcsetattr(fileno(stdin), TCSAFLUSH, &orig); |
|
printf("\n"); |
|
backup_termios = NULL; |
|
|
|
for (p = (char*)password; *p; p++) |
|
; |
|
for ( ; p > (char*)password ; p--) |
|
if (*p == '\n' || *p == '\r') *p = 0; |
|
|
|
return password; |
|
} |
|
|
|
// password_eval(command, *status) |
|
// Get password from a system command. |
|
// The string must be freed after use. |
|
static char *password_eval(const char *command, int *status) |
|
{ |
|
#define MAX_PWD 100 |
|
char *pwd; |
|
FILE *outfp = popen(command, "r"); |
|
if (outfp == NULL) { |
|
scr_log_print(LPRINT_NORMAL, |
|
"** ERROR: Failed to execute password_eval command."); |
|
*status = -1; |
|
return NULL; |
|
} |
|
|
|
pwd = g_new0(char, MAX_PWD); |
|
if (fgets(pwd, MAX_PWD, outfp) == NULL) { |
|
scr_log_print(LPRINT_NORMAL, |
|
"** ERROR: Failed to read from password_eval command."); |
|
g_free(pwd); |
|
*status = -1; |
|
return NULL; |
|
} |
|
|
|
int res = pclose(outfp); |
|
if (res != 0 && errno != ECHILD) { |
|
scr_log_print(LPRINT_NORMAL, |
|
"** ERROR: Password evaluation command exited with error %d.", |
|
res); |
|
if (res == -1) { |
|
scr_log_print(LPRINT_NORMAL, " errno=%d", errno); |
|
} |
|
g_free(pwd); |
|
*status = res; |
|
return NULL; |
|
} |
|
|
|
// Strip trailing whitespaces and newlines |
|
size_t i = strlen(pwd); |
|
while (i && isspace(pwd[i-1])) { |
|
i--; |
|
} |
|
pwd[i] = '\0'; |
|
return pwd; |
|
} |
|
|
|
static void credits(void) |
|
{ |
|
const char *v_fmt = "MCabber %s -- Email: mcabber [at] lilotux [dot] net\n"; |
|
char *v = mcabber_version(); |
|
printf(v_fmt, v); |
|
scr_LogPrint(LPRINT_NORMAL|LPRINT_NOTUTF8, v_fmt, v); |
|
g_free(v); |
|
} |
|
|
|
static void compile_options(void) |
|
{ |
|
puts("Installation data directory: " DATA_DIR "\n"); |
|
#ifdef HAVE_UNICODE |
|
puts("Compiled with unicode support."); |
|
#endif |
|
#ifdef HAVE_GPGME |
|
puts("Compiled with GPG support."); |
|
#endif |
|
#ifdef HAVE_LIBOTR |
|
puts("Compiled with OTR support."); |
|
#endif |
|
#ifdef ENABLE_DEBUG |
|
puts("Compiled with debugging support."); |
|
#endif |
|
} |
|
|
|
static void main_init_pgp(void) |
|
{ |
|
#ifdef HAVE_GPGME |
|
const char *pk, *pp; |
|
char *typed_passwd = NULL; |
|
char *p; |
|
bool pgp_invalid = FALSE; |
|
bool pgp_agent; |
|
int retries; |
|
|
|
pk = settings_opt_get("pgp_private_key"); |
|
|
|
if (!pk) |
|
scr_LogPrint(LPRINT_LOGNORM, "WARNING: unknown PGP private key"); |
|
|
|
if (gpg_init(pk, NULL)) { |
|
scr_LogPrint(LPRINT_LOGNORM, "WARNING: Could not initialize PGP."); |
|
return; |
|
} |
|
|
|
// We're done if the PGP engine version is > 1 |
|
// since the agent is mandatory and password mechanism is external. |
|
if (!gpg_is_version1()) |
|
return; |
|
|
|
|
|
p = getenv("GPG_AGENT_INFO"); |
|
pgp_agent = (p && strchr(p, ':')); |
|
|
|
if (settings_opt_get("pgp_passphrase_retries")) |
|
retries = settings_opt_get_int("pgp_passphrase_retries"); |
|
else |
|
retries = 2; |
|
|
|
pp = settings_opt_get("pgp_passphrase"); |
|
|
|
if (!pk) { |
|
pgp_invalid = TRUE; |
|
} else if (!(pp || pgp_agent)) { |
|
// Request PGP passphrase |
|
pp = typed_passwd = ask_password("your PGP passphrase"); |
|
} |
|
gpg_set_passphrase(pp); |
|
// Erase password from the settings array |
|
if (pp) { |
|
memset((char*)pp, 0, strlen(pp)); |
|
if (typed_passwd) |
|
g_free(typed_passwd); |
|
else |
|
settings_set(SETTINGS_TYPE_OPTION, "pgp_passphrase", NULL); |
|
} |
|
if (!pgp_agent && pk && pp && gpg_test_passphrase()) { |
|
// Let's check the pasphrase |
|
int i; |
|
for (i = 1; retries < 0 || i <= retries; i++) { |
|
typed_passwd = ask_password("your PGP passphrase"); // Ask again... |
|
if (typed_passwd) { |
|
gpg_set_passphrase(typed_passwd); |
|
memset(typed_passwd, 0, strlen(typed_passwd)); |
|
g_free(typed_passwd); |
|
} |
|
if (!gpg_test_passphrase()) |
|
break; // Ok |
|
} |
|
if (i > retries) |
|
pgp_invalid = TRUE; |
|
} |
|
if (pgp_invalid) |
|
scr_LogPrint(LPRINT_LOGNORM, "WARNING: PGP key/pass invalid"); |
|
#else /* not HAVE_GPGME */ |
|
scr_LogPrint(LPRINT_LOGNORM, "WARNING: not compiled with PGP support"); |
|
#endif /* HAVE_GPGME */ |
|
} |
|
|
|
void mcabber_set_terminate_ui(void) |
|
{ |
|
terminate_ui = TRUE; |
|
} |
|
|
|
typedef struct { |
|
GSource source; |
|
GPollFD pollfd; |
|
} mcabber_source_t; |
|
|
|
static gboolean mcabber_source_prepare(GSource *source, gint *timeout) |
|
{ |
|
*timeout = -1; |
|
return FALSE; |
|
} |
|
|
|
static gboolean mcabber_source_check(GSource *source) |
|
{ |
|
mcabber_source_t *mc_source = (mcabber_source_t *) source; |
|
gushort revents = mc_source->pollfd.revents; |
|
if (revents) |
|
return TRUE; |
|
return FALSE; |
|
} |
|
|
|
static gboolean keyboard_activity(void) |
|
{ |
|
keycode_t kcode; |
|
|
|
if (terminate_ui) { |
|
return FALSE; |
|
} |
|
scr_do_update(); |
|
scr_getch(&kcode); |
|
|
|
while (kcode.value != ERR) { |
|
scr_process_key(kcode); |
|
scr_getch(&kcode); |
|
} |
|
scr_check_auto_away(FALSE); |
|
|
|
return TRUE; |
|
} |
|
|
|
static gboolean mcabber_source_dispatch(GSource *source, GSourceFunc callback, |
|
gpointer udata) { |
|
return keyboard_activity(); |
|
} |
|
|
|
static GSourceFuncs mcabber_source_funcs = { |
|
mcabber_source_prepare, |
|
mcabber_source_check, |
|
mcabber_source_dispatch, |
|
NULL, |
|
NULL, |
|
NULL |
|
}; |
|
|
|
int main(int argc, char **argv) |
|
{ |
|
char *configFile = NULL; |
|
const char *optstring; |
|
int optval, optval2; |
|
int ret; |
|
|
|
credits(); |
|
|
|
signal(SIGTERM, sig_handler); |
|
signal(SIGINT, sig_handler); |
|
signal(SIGHUP, sig_handler); |
|
signal(SIGCHLD, sig_handler); |
|
#ifdef USE_SIGWINCH |
|
signal(SIGWINCH, sig_handler); |
|
#endif |
|
signal(SIGPIPE, SIG_IGN); |
|
|
|
/* Parse command line options */ |
|
while (1) { |
|
int c = getopt(argc, argv, "hVf:"); |
|
if (c == -1) { |
|
break; |
|
} else |
|
switch (c) { |
|
case 'h': |
|
case '?': |
|
printf("Usage: %s [-h|-V|-f mcabberrc_file]\n\n", argv[0]); |
|
return (c == 'h' ? 0 : -1); |
|
case 'V': |
|
compile_options(); |
|
return 0; |
|
case 'f': |
|
configFile = g_strdup(optarg); |
|
break; |
|
} |
|
} |
|
|
|
if (optind < argc) { |
|
fprintf(stderr, "Usage: %s [-h|-V|-f mcabberrc_file]\n\n", argv[0]); |
|
return -1; |
|
} |
|
|
|
/* Initialize command system, roster and default key bindings */ |
|
compl_init_system(); |
|
cmd_init(); |
|
roster_init(); |
|
settings_init(); |
|
scr_init_bindings(); |
|
scr_init_settings(); |
|
caps_init(); |
|
/* Initialize charset */ |
|
scr_init_locale_charset(); |
|
ut_init_debug(); |
|
help_init(); |
|
|
|
/* Parsing config file... */ |
|
ret = cfg_read_file(configFile, TRUE); |
|
/* free() configFile if it has been allocated during options parsing */ |
|
g_free(configFile); |
|
/* Leave if there was an error in the config. file */ |
|
if (ret == -2) |
|
exit(EXIT_FAILURE); |
|
|
|
#ifdef OpenBSD |
|
if (pledge("stdio rpath wpath cpath fattr inet dns tty proc exec", NULL) == |
|
-1) { |
|
fprintf(stderr, "Cannot pledge: %s\n", strerror(errno)); |
|
exit(EXIT_FAILURE); |
|
} |
|
#endif |
|
|
|
/* Display configuration settings */ |
|
{ |
|
const char *p; |
|
if ((p = settings_opt_get("server")) != NULL) |
|
scr_log_print(LPRINT_NORMAL, "Server: %s", p); |
|
if ((p = settings_opt_get("jid")) != NULL) { |
|
scr_log_print(LPRINT_NORMAL, "User JID: %s", p); |
|
} |
|
} |
|
|
|
/* If no password is stored, we ask for it before entering |
|
ncurses mode -- unless the username is unknown. */ |
|
if (settings_opt_get("jid") && !settings_opt_get("password")) { |
|
const char *pass_eval = settings_opt_get("password_eval"); |
|
if (pass_eval) { |
|
int status = 0; |
|
char *pwd = password_eval(pass_eval, &status); |
|
if (status == 0 && pwd) { |
|
settings_set(SETTINGS_TYPE_OPTION, "password", pwd); |
|
} |
|
g_free(pwd); |
|
} |
|
// If the password is still unset, ask the user... |
|
if (!settings_opt_get("password")) { |
|
char *pwd = ask_password("your Jabber password"); |
|
settings_set(SETTINGS_TYPE_OPTION, "password", pwd); |
|
g_free(pwd); |
|
} |
|
} |
|
|
|
/* Initialize PGP system |
|
We do it before ncurses initialization because we may need to request |
|
a passphrase. */ |
|
if (settings_opt_get_int("pgp")) |
|
main_init_pgp(); |
|
|
|
/* Initialize N-Curses */ |
|
scr_LogPrint(LPRINT_DEBUG, "Initializing N-Curses..."); |
|
scr_init_curses(); |
|
scr_draw_main_window(TRUE); |
|
|
|
optval = (settings_opt_get_int("logging") > 0); |
|
optval2 = (settings_opt_get_int("load_logs") > 0); |
|
if (optval || optval2) |
|
hlog_enable(optval, settings_opt_get("logging_dir"), optval2); |
|
|
|
optstring = settings_opt_get("events_command"); |
|
if (optstring) |
|
hk_ext_cmd_init(optstring); |
|
|
|
optstring = settings_opt_get("roster_display_filter"); |
|
if (optstring) |
|
scr_roster_display(optstring); |
|
// Empty filter isn't allowed... |
|
if (!buddylist_get_filter()) |
|
scr_roster_display("*"); |
|
|
|
chatstates_disabled = settings_opt_get_int("disable_chatstates"); |
|
|
|
/* Initialize FIFO named pipe */ |
|
fifo_init(); |
|
|
|
/* Load previous roster state */ |
|
hlog_load_state(); |
|
|
|
main_context = g_main_context_default(); |
|
|
|
if (ret < 0) { |
|
scr_LogPrint(LPRINT_NORMAL, "No configuration file has been found."); |
|
scr_show_buddy_window(); |
|
} else { |
|
/* Connection */ |
|
if (xmpp_connect()) |
|
scr_show_buddy_window(); |
|
} |
|
|
|
// Initial drawing |
|
scr_draw_roster(); |
|
scr_do_update(); |
|
|
|
{ // add keypress processing source |
|
GSource *mc_source = g_source_new(&mcabber_source_funcs, |
|
sizeof(mcabber_source_t)); |
|
GPollFD *mc_pollfd = &(((mcabber_source_t *)mc_source)->pollfd); |
|
mc_pollfd->fd = STDIN_FILENO; |
|
mc_pollfd->events = POLLIN|POLLERR|POLLPRI; |
|
mc_pollfd->revents = 0; |
|
g_source_add_poll(mc_source, mc_pollfd); |
|
g_source_attach(mc_source, main_context); |
|
|
|
scr_LogPrint(LPRINT_DEBUG, "Entering into main loop..."); |
|
|
|
while(!terminate_ui) { |
|
if (g_main_context_iteration(main_context, TRUE) == FALSE) |
|
keyboard_activity(); |
|
#ifdef USE_SIGWINCH |
|
if (sigwinch) { |
|
sigwinch_resize(); |
|
sigwinch = FALSE; |
|
} |
|
#endif |
|
scr_draw_roster(); |
|
scr_do_update(); |
|
} |
|
|
|
g_source_destroy(mc_source); |
|
g_source_unref(mc_source); |
|
} |
|
|
|
evs_deinit(); |
|
fifo_deinit(); |
|
#ifdef HAVE_LIBOTR |
|
otr_terminate(); |
|
#endif |
|
xmpp_disconnect(); |
|
#ifdef HAVE_GPGME |
|
gpg_terminate(); |
|
#endif |
|
|
|
scr_terminate_curses(); |
|
/* Save pending message state */ |
|
hlog_save_state(); |
|
caps_free(); |
|
settings_free(); |
|
|
|
printf("\n\nThanks for using mcabber!\n"); |
|
|
|
return 0; |
|
} |
|
|
|
/* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */
|
|
|