view mcabber/mcabber/main.c @ 2223:965e0282c128

Backed out changeset fa516ef22145 Turns out that in this hash the value pointer is the same as the key pointer, so there's no need to free both the key and the value.
author Mikael Berthe <mikael@lilotux.net>
date Fri, 06 Nov 2015 22:31:40 +0100
parents 8811fe9d6ef0
children 1afa40f1d80d
line wrap: on
line source

/*
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

#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 <config.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"

#ifndef MODULES_ENABLE
# include "fifo.h"
#endif

#ifdef MODULES_ENABLE
# include "compl.h"
# include "modules.h"
#endif

#ifdef ENABLE_HGCSET
# include "hgcset.h"
#endif

#ifndef WAIT_ANY
# define WAIT_ANY -1
#endif

static unsigned int terminate_ui;
GMainContext *main_context;

static struct termios *backup_termios;

char *mcabber_version(void)
{
  char *ver;
#ifdef HGCSET
  ver = g_strdup_printf("%s (%s)", PACKAGE_VERSION, HGCSET);
#else
  ver = g_strdup(PACKAGE_VERSION);
#endif
  return ver;
}

static void mcabber_terminate(const char *msg)
{
#ifndef MODULES_ENABLE
  fifo_deinit();
#endif
  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) {
    if (scr_curses_status())
      ungetch(KEY_RESIZE);
#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 MODULES_ENABLE
  printf("Compiled with modules support. (API %s:%d-%d)\n",
         MCABBER_BRANCH, MCABBER_API_MIN, MCABBER_API_VERSION);
# ifdef PKGLIB_DIR
  puts("Modules directory: " PKGLIB_DIR);
# endif
#endif
#ifdef HAVE_GPGME
  puts("Compiled with GPG support.");
#endif
#ifdef HAVE_LIBOTR
  puts("Compiled with OTR support.");
#endif
#ifdef WITH_ENCHANT
  puts("Compiled with Enchant support.");
#endif
#ifdef WITH_ASPELL
  puts("Compiled with Aspell 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 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();
#ifdef MODULES_ENABLE
  modules_init();
#endif
  /* 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);

  /* 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);

#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
  /* Initialize spelling */
  if (settings_opt_get_int("spell_enable")) {
    spellcheck_init();
  }
#endif

  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");

#ifndef MODULES_ENABLE
  /* Initialize FIFO named pipe */
  fifo_init();
#endif

  /* 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();
      if (update_roster)
        scr_draw_roster();
      scr_do_update();
    }

    g_source_destroy(mc_source);
    g_source_unref(mc_source);
  }

  evs_deinit();
#ifdef MODULES_ENABLE
  modules_deinit();
#endif
#ifndef MODULES_ENABLE
  fifo_deinit();
#endif
#ifdef HAVE_LIBOTR
  otr_terminate();
#endif
  xmpp_disconnect();
#ifdef HAVE_GPGME
  gpg_terminate();
#endif
#if defined(WITH_ENCHANT) || defined(WITH_ASPELL)
  /* Deinitialize spelling */
  if (settings_opt_get_int("spell_enable"))
    spellcheck_deinit();
#endif

  scr_terminate_curses();
  /* Save pending message state */
  hlog_save_state();
  caps_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... */