Personal copy of mcabber
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

/*
* 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... */