gkrellm/src/mail.c

4496 lines
122 KiB
C

/* GKrellM
| Copyright (C) 1999-2014 Bill Wilson
|
| Author: Bill Wilson billw@gkrellm.net
| Latest versions might be found at: http://gkrellm.net
|
|
| GKrellM 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 3 of the License, or
| (at your option) any later version.
|
| GKrellM 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/
|
|
| Additional permission under GNU GPL version 3 section 7
|
| If you modify this program, or any covered work, by linking or
| combining it with the OpenSSL project's OpenSSL library (or a
| modified version of that library), containing parts covered by
| the terms of the OpenSSL or SSLeay licenses, you are granted
| additional permission to convey the resulting work.
| Corresponding Source for a non-source form of such a combination
| shall include the source code for the parts of OpenSSL used as well
| as that of the covered work.
*/
#include "gkrellm.h"
#include "gkrellm-private.h"
#include <utime.h>
#include <sys/time.h>
#include <errno.h>
#include "pixmaps/mail/decal_mail.xpm"
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
#define HAVE_MD5_H
#endif
#if defined(HAVE_GNUTLS)
#include <gnutls/openssl.h>
#define MD5Init MD5_Init
#define MD5Update MD5_Update
#define MD5Final MD5_Final
#if GNUTLS_VERSION_NUMBER <= 0x020b00
#include <gcrypt.h>
/* gcrypt mutex setup is only needed for GnuTLS < 2.12 */
static int gk_gcry_glib_mutex_init (void **priv) {
GMutex *lock = g_mutex_new();
if (!lock)
return ENOMEM;
*priv = lock;
return 0;
}
static int gk_gcry_glib_mutex_destroy (void **lock) {
if (!lock || !*lock)
return 1; // what to return?
g_mutex_free((GMutex *)*lock);
return 0;
}
static int gk_gcry_glib_mutex_lock (void **lock) {
if (!lock || !*lock)
return 1; // what to return?
g_mutex_lock((GMutex*)*lock);
return 0;
}
static int gk_gcry_glib_mutex_unlock (void **lock) {
if (!lock || !*lock)
return 1; // what to return?
g_mutex_unlock((GMutex*)*lock);
return 0;
}
static struct gcry_thread_cbs gk_gcry_threads_glib = {
(GCRY_THREAD_OPTION_USER | (GCRY_THREAD_OPTION_VERSION << 8)),
NULL /* init() */,
gk_gcry_glib_mutex_init,
gk_gcry_glib_mutex_destroy,
gk_gcry_glib_mutex_lock,
gk_gcry_glib_mutex_unlock,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};
#endif
#else
#if defined(HAVE_SSL)
#include <openssl/ssl.h>
#include <openssl/md5.h>
#define MD5Init MD5_Init
#define MD5Update MD5_Update
#define MD5Final MD5_Final
#else
#if defined(HAVE_MD5_H)
#include <md5.h>
#else
#include "md5.h"
#endif
#endif
#endif
#if defined(HAVE_NTLM)
#include <ntlm.h>
#endif
#define MUTE_FLAG -1
/* msg_count_mode has 3 states
*/
#define MSG_NEW_TOTAL_COUNT 0
#define MSG_NEW_COUNT 1
#define MSG_NO_COUNT 2
/* animation_mode states
*/
#define ANIMATION_NONE 0
#define ANIMATION_ENVELOPE 1
#define ANIMATION_PENGUIN 2
#define ANIMATION_BOTH 3
/* # of seconds to wait for a response from a POP3 or IMAP server
*/
#define TCP_TIMEOUT 30
#define DEFAULT_POP3_PORT "110"
#define DEFAULT_IMAP_PORT "143"
#define DEFAULT_IMAPS_PORT "993"
#define DEFAULT_POP3S_PORT "995"
/* A mailbox type has bits encoding how to check the mailbox (inline code
| check or threaded check).
| Threaded checks and the fetch program are remote checks
*/
#define MBOX_CHECK_FETCH 0x1000
#define MBOX_CHECK_INLINE 0x2000
#define MBOX_CHECK_THREADED 0x4000
#define MBOX_CHECK_TYPE_MASK 0xf000
/* Counts for mailboxes created and checked in other plugins can be shown */
#define MBOX_EXTERNAL 0x10
/* Mailboxes internally checked and created via the Mail->Mailboxes config */
#define MBOX_INTERNAL 0x20
/* Here's the list of all the mailbox types the Mail monitor knows about.
| The MBOX_FETCH is a pseudo internal mailbox where the counts read from
| the fetch program are kept. Additionally MBOX_FETCH_TOOLTIP types
| are constructed just so the fetch programs output lines can be
| reported in a tooltip. Real mailboxes that GKrellM creates in its
| config and knows how to check have MBOX_INTERNAL set. And
| finally there can be external (plugin) mailboxes created which
| can have their check function called at the update intervals. If the
| plugin reports back the count results, the animation/sound can be
| triggered for the plugin. (Don't know if EXTERNAL guys will ever be used)
| Internal mailboxes can be remote or local. Remote mailboxes have an
| authorization protocol that subdivides them into types. Local mailboxes
| currently have separate mboxtype values but I may later group them
| into a MBOX_LOCAL type with a subdivision protocol like is currently
| done for remote mailboxes.
*/
#define MBOX_FETCH (MBOX_CHECK_FETCH)
#define MBOX_MBOX (MBOX_CHECK_INLINE | MBOX_INTERNAL | 0)
#define MBOX_MAILDIR (MBOX_CHECK_INLINE | MBOX_INTERNAL | 1)
#define MBOX_MH_DIR (MBOX_CHECK_INLINE | MBOX_INTERNAL | 2)
#define MBOX_REMOTE (MBOX_CHECK_THREADED | MBOX_INTERNAL | 3)
#define MBOX_FETCH_TOOLTIP (6)
#define MBOX_LOCAL_PLUGIN (MBOX_CHECK_INLINE | MBOX_EXTERNAL)
#define MBOX_REMOTE_PLUGIN (MBOX_CHECK_THREADED | MBOX_EXTERNAL)
#define PROTO_POP3 0
#define PROTO_IMAP 1
#define AUTH_PLAINTEXT 0
#define AUTH_USER AUTH_PLAINTEXT /* POP3 only */
#define AUTH_APOP 1 /* POP3 only */
#define AUTH_LOGIN AUTH_PLAINTEXT /* IMAP4 only */
#define AUTH_CRAM_MD5 2
#define AUTH_NTLM 3
#define SSL_NONE 0
#define SSL_TRANSPORT 1
#define SSL_STARTTLS 2
/* Authorization protocol strings to write into the config for remote
| mailboxes.
*/
typedef struct
{
gchar *string;
gint protocol;
gint authmech;
}
AuthType;
static AuthType auth_strings[] =
{
{ "POP3", PROTO_POP3, AUTH_USER },
{ "POP3_(APOP)", PROTO_POP3, AUTH_APOP },
{ "POP3_(CRAM-MD5)",PROTO_POP3, AUTH_CRAM_MD5 },
#ifdef HAVE_NTLM
{ "POP3_(NTLM)", PROTO_POP3, AUTH_NTLM },
#endif
{ "IMAP", PROTO_IMAP, AUTH_LOGIN },
{ "IMAP_(CRAM-MD5)",PROTO_IMAP, AUTH_CRAM_MD5 },
#ifdef HAVE_NTLM
{ "IMAP_(NTLM)", PROTO_IMAP, AUTH_NTLM },
#endif
{ NULL, -1, -1 }
};
/* Save local mailbox type strings in the config in case I later change
| to an option_menu selection for subdividing a MBOX_LOCAL type.
| Currently local mailbox types are determined in get_local_mboxtype().
*/
static gchar *mbox_strings[3] =
{
"mbox",
"Maildir",
"MH_mail"
};
static GkrellmMonitor *mon_mail;
typedef struct
{
gchar *path,
*homedir_path;
gchar *server;
gchar *username;
gchar *password;
gchar *imapfolder;
gint mboxtype;
gint protocol;
gint authmech;
gint port;
gint use_ssl; /* Always SSL_NONE if !HAVE_SSL */
}
MailAccount;
typedef struct
{
MailAccount *account;
gboolean busy;
GString *tcp_in;
gboolean (*check_func)();
gpointer data; /* For external mailboxes (in plugins) */
GThread* thread;
gint mail_count;
gint new_mail_count;
gint old_mail_count;
gint prev_mail_count,
prev_new_mail_count;
time_t last_mtime;
off_t last_size;
gboolean is_internal; /* Internal mail message (ie: localmachine) */
gboolean need_animation,
prev_need_animation;
gchar *warn_msg;
gchar *uidl;
gboolean warned;
void *private;
}
Mailbox;
static GList *mailbox_list;
typedef struct
{
gchar *command;
GString *read_gstring; /* Bytes read from pipe stored here */
gint pipe;
}
Mailproc;
typedef struct
{
gint fd;
#ifdef HAVE_SSL
SSL *ssl;
SSL_CTX *ssl_ctx;
#endif
}
ConnInfo;
Mailbox *mail_fetch; /* Internal mailbox: fetch command */
static Mailproc mail_user_agent;
static gchar *mail_notify; /* Sound */
static GkrellmPiximage *decal_mail_piximage;
static gint run_animation,
decal_frame;
static gint remote_check_timeout = 5; /* Minutes */
static gint local_check_timeout = 4; /* Seconds */
static gboolean fetch_check_is_local;
static GkrellmPanel *mail;
#if !GTK_CHECK_VERSION(2,12,0)
static GtkTooltips *tooltip;
#endif
static GkrellmDecalbutton *mua_button;
static gboolean enable_mail,
mute_mode,
super_mute_mode,
cont_animation_mode,
mua_inhibit_mode, /* Inhibit checking if MUA launched */
enable_multimua, /* allow multiple MUA instances */
count_mode,
fetch_check_only_mode,
reset_remote_mode,
unseen_is_new, /* Accessed but unread */
local_supported = TRUE;
static gboolean mh_seq_ignore,
have_mh_sequences,
checking_mh_mail;
static gint animation_mode = ANIMATION_BOTH;
static gboolean force_mail_check;
static gint new_mail_count, total_mail_count;
static gint check_timeout;
static gint show_tooltip = FALSE;
static gint anim_frame,
anim_dir,
anim_pause;
static gint style_id;
#ifdef HAVE_SSL
#ifndef HAVE_GNUTLS
static GMutex **ssl_locks;
static void
ssl_locking_cb(int mode, int n, const char *file, int line)
{
if (mode & CRYPTO_LOCK)
g_mutex_lock(ssl_locks[n]);
else
g_mutex_unlock(ssl_locks[n]);
}
#endif
#endif
/* This may be called from gkrellm_sys_main_init()
*/
void
gkrellm_mail_local_unsupported(void)
{
local_supported = FALSE;
}
GThread *
gkrellm_mail_get_active_thread(void)
{
GList *list;
Mailbox *mbox;
GThread *thread;
for (list = mailbox_list; list; list = list->next)
{
mbox = (Mailbox *) list->data;
thread = mbox->thread;
if (thread)
return thread;
}
return NULL;
}
static void
free_account(MailAccount *account)
{
if (!account)
return;
g_free(account->path);
g_free(account->homedir_path);
g_free(account->server);
g_free(account->username);
g_free(account->password);
g_free(account->imapfolder);
g_free(account);
}
static void
free_mailbox(Mailbox *mbox)
{
/* If user changes mailbox config list while a mailbox thread is busy,
| freeing the mbox can cause a segfault. Rare, so allow the leak.
*/
if (mbox->busy)
return;
free_account(mbox->account);
g_free(mbox->warn_msg);
g_free(mbox);
}
static gboolean
format_remote_mbox_name(Mailbox *mbox, gchar *buf, size_t len)
{
MailAccount *account = mbox->account;
if (account->imapfolder && *account->imapfolder)
snprintf(buf, len, "%s-%s@%s", account->username,
account->imapfolder, account->server);
else if (account->server)
snprintf(buf, len, "%s@%s", account->username, account->server);
else if (account->username)
snprintf(buf, len, "%s", account->username);
else
{
snprintf(buf, len, "??");
return FALSE;
}
return TRUE;
}
/* Make tooltip visible/invisible and fill it with mailbox names
| containing new mail.
*/
static void
update_tooltip(void)
{
GList *list;
Mailbox *mbox;
MailAccount *account;
GString *mboxes = NULL;
gchar buf[128];
if (show_tooltip)
{
mboxes = g_string_sized_new(512);
for (list = mailbox_list; list; list = list->next)
{
mbox = (Mailbox *) list->data;
account = mbox->account;
if (mbox->new_mail_count > 0)
{
if (( account->mboxtype == MBOX_MBOX
|| account->mboxtype == MBOX_MAILDIR
|| account->mboxtype == MBOX_MH_DIR
|| account->mboxtype == MBOX_LOCAL_PLUGIN
|| account->mboxtype == MBOX_REMOTE_PLUGIN
) && account->path
)
snprintf(buf, sizeof(buf), "%s", account->homedir_path ?
account->homedir_path : account->path);
else if (! format_remote_mbox_name(mbox, buf, sizeof(buf)))
continue; /* Can't get a name, so no tooltip for you! */
if (mboxes->len > 0)
g_string_append_c(mboxes, '\n');
g_string_append(mboxes, buf);
if (count_mode == MSG_NEW_TOTAL_COUNT)
snprintf(buf, sizeof(buf), "(%d/%d)",
mbox->new_mail_count, mbox->mail_count);
else
snprintf(buf, sizeof(buf), "(%d)", mbox->new_mail_count);
g_string_append(mboxes, buf);
}
}
}
if (show_tooltip && mboxes && mboxes->len > 0)
{
#if GTK_CHECK_VERSION(2,12,0)
gtk_widget_set_tooltip_text(mail->drawing_area, mboxes->str);
#else
gtk_tooltips_set_tip(tooltip, mail->drawing_area, mboxes->str, "");
gtk_tooltips_enable(tooltip);
#endif
}
else
{
#if GTK_CHECK_VERSION(2,12,0)
gtk_widget_set_has_tooltip(mail->drawing_area, FALSE);
#else
gtk_tooltips_disable(tooltip);
#endif
}
if (mboxes)
g_string_free(mboxes, TRUE);
}
/* Look at a From line to see if it is valid, lines look like:
| From sending_address dayofweek month dayofmonth timeofday year
| eg: From billw@gkrellm.net Fri Oct 22 13:52:49 2010
*/
static gint
is_From_line(Mailbox *mbox, gchar *buf)
{
gchar sender[512];
gint dayofmonth = 0;
if (strncmp(buf, "From ", 5))
return FALSE;
/* In case sending address missing, look for a day of month
| number in field 3 or 4 (0 based).
*/
sender[0] = '\0';
if (sscanf(buf, "%*s %*s %*s %d", &dayofmonth) != 1)
{
if (sscanf(buf, "%*s %511s %*s %*s %d", sender, &dayofmonth) != 2)
return FALSE;
}
if (dayofmonth < 1 || dayofmonth > 31)
return FALSE;
if (strcmp(sender, "MAILER-DAEMON") == 0)
mbox->is_internal = TRUE;
return TRUE;
}
/* Check if this is a Content-Type-line. If it contains a boundary
| field, copy boundary string to buffer (including two leading and
| trailing dashes marking the end of a multipart mail) and return
| true. Otherwise, return false.
*/
static gint
is_multipart_mail(gchar *buf, gchar *separator)
{
gchar *fieldstart;
gchar *sepstart;
gint seplen;
if (strncmp(buf, "Content-Type: ", 14) != 0)
return FALSE;
if (strncmp(&buf[14], "multipart/", 10) != 0)
return FALSE;
fieldstart = &buf[14];
while (*fieldstart!=0)
{
while (*fieldstart!=0 && *fieldstart!=';')
fieldstart++;
if (*fieldstart==';') fieldstart++;
while (*fieldstart!=0 && *fieldstart==' ')
fieldstart++;
if (strncmp(fieldstart, "boundary=", 9) == 0)
{
sepstart = fieldstart + 9;
if (sepstart[0]=='"')
{
sepstart++;
seplen = 0;
while (sepstart[seplen]!='"' && sepstart[seplen]>=32)
seplen++;
}
else
{
seplen = 0;
while (sepstart[seplen]!=';' && sepstart[seplen]>32)
seplen++;
}
strcpy(separator,"--");
strncpy(&separator[2],sepstart,seplen);
strcpy(&separator[seplen+2],"--");
return TRUE;
}
}
return FALSE;
}
/* Hide a password that is embedded in a string.
*/
static void
hide_password(gchar *line, gint offset, const gchar *pass)
{
gint n;
n = strlen(pass);
while (n--)
line[offset + n] = '*';
}
static gint
read_select(gint fd, gchar *buf, size_t size, time_t timeout)
{
fd_set readfds;
struct timeval tv;
gint n = 0;
gint s;
do
{
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
tv.tv_sec = timeout;
tv.tv_usec = 0;
if ((s = select(fd+1, &readfds, NULL, NULL, &tv)) > 0)
#if defined(WIN32)
n = recv(fd, buf, size, 0);
#else
n = read(fd, buf, size);
#endif
} while (s < 0 && errno == EINTR);
return n;
}
/* Read \r\n terminated lines from a remote IMAP or POP3 mail server,
*/
static void
tcp_getline(ConnInfo *conn, Mailbox *mbox)
{
gchar buf[256];
gint n;
gchar *s;
if (mbox->tcp_in)
mbox->tcp_in = g_string_truncate(mbox->tcp_in, 0);
else
mbox->tcp_in = g_string_new("");
s = buf;
for (;;)
{
#ifdef HAVE_SSL
if (conn->ssl)
n = SSL_read(conn->ssl, s, 1);
else
#endif
n = read_select(conn->fd, s, 1, TCP_TIMEOUT);
if (n <= 0)
{
if (n < 0)
g_warning("tcp_getline: %s", g_strerror(errno));
break;
}
*(s+1) = '\0';
if (*s++ == '\n')
break;
if (s >= buf + sizeof(buf) - 2)
{
g_string_append(mbox->tcp_in, buf);
s = buf;
}
}
if (s > buf)
g_string_append(mbox->tcp_in, buf);
if (_GK.debug_level & DEBUG_MAIL)
{
format_remote_mbox_name(mbox, buf, sizeof(buf));
g_debug("server_response( %s )<%d>:%s", buf,
(gint) mbox->tcp_in->len, mbox->tcp_in->str);
}
}
static void
tcp_putline(ConnInfo *conn, gchar *line)
{
gint n;
#ifdef HAVE_SSL
if (conn->ssl)
n = SSL_write(conn->ssl, line, strlen(line));
else
#endif
{
#if defined(WIN32)
n = send(conn->fd, line, strlen(line), 0);
#else
n = write(conn->fd, line, strlen(line));
#endif
}
if (n < 0)
g_warning("tcp_putline: %s", g_strerror(errno));
}
/* Get a server response line and verify the beginning of the line
| matches a string.
*/
static gboolean
server_response(ConnInfo *conn, Mailbox *mbox, gchar *match)
{
tcp_getline(conn, mbox);
return (!strncmp(match, mbox->tcp_in->str, strlen(match)) ? TRUE : FALSE);
}
/* Get a imap server completion result response for a tagged command.
| Skip over any untagged responses the server may send.
*/
static gboolean
imap_completion_result(ConnInfo *conn, Mailbox *mbox, gchar *tag)
{
while (1)
{
tcp_getline(conn, mbox);
if (*(mbox->tcp_in->str) == '*') /* untagged response */
continue;
return (!strncmp(tag, mbox->tcp_in->str, strlen(tag)) ? TRUE : FALSE);
}
}
static void
server_command(ConnInfo *conn, Mailbox *mbox, gchar *line)
{
gchar buf[128];
tcp_putline(conn, line);
if (_GK.debug_level & DEBUG_MAIL)
{
format_remote_mbox_name(mbox, buf, sizeof(buf));
g_debug("server_command( %s ):%s", buf, line);
}
}
static gchar *tcp_error_message[] =
{
N_("Unable to connect."),
N_("Bad response after connect."),
N_("Bad response after username."),
N_("Bad response after password."),
N_("Bad response after STAT or STATUS."),
N_("Bad response after UIDL."),
N_("Bad APOP response after connect."),
N_("Bad CRAM_MD5 response after connect."),
};
static void
tcp_close(ConnInfo *conn)
{
#ifdef HAVE_SSL
#ifndef HAVE_GNUTLS
SSL_SESSION *session;
#endif
#endif
if (conn->fd != -1)
{
#ifdef WIN32
closesocket(conn->fd);
#else
close(conn->fd);
#endif
conn->fd = -1;
}
#ifdef HAVE_SSL
if (conn->ssl)
{
#ifndef HAVE_GNUTLS
session = SSL_get_session(conn->ssl);
if (session)
SSL_CTX_remove_session(conn->ssl_ctx, session);
#endif
SSL_free(conn->ssl);
conn->ssl = NULL;
}
if (conn->ssl_ctx)
{
SSL_CTX_free(conn->ssl_ctx);
conn->ssl_ctx = NULL;
}
#endif
}
static gboolean
tcp_warn(Mailbox *mbox, gchar *message, gboolean warn)
{
gchar buf[128];
if (_GK.debug_level & DEBUG_MAIL)
{
format_remote_mbox_name(mbox, buf, sizeof(buf));
g_debug(_("Mail TCP Error: %s - %s\n"), buf, _(message));
}
if (warn && !mbox->warned)
{
g_free(mbox->warn_msg);
format_remote_mbox_name(mbox, buf, sizeof(buf));
mbox->warn_msg = g_strdup_printf("%s\n%s\n%s\n", buf,
_(message), mbox->tcp_in->str);
}
return FALSE;
}
static gboolean
tcp_shutdown(ConnInfo *conn, Mailbox *mbox, gchar *message, gboolean warn)
{
tcp_close(conn);
return tcp_warn(mbox, message, warn);
}
#ifdef HAVE_SSL
static gboolean
ssl_negotiate(ConnInfo *conn, Mailbox *mbox)
{
const SSL_METHOD *ssl_method;
if (mbox->account->use_ssl == SSL_TRANSPORT)
ssl_method = SSLv23_client_method();
else
ssl_method = TLSv1_client_method();
if (ssl_method == NULL)
return tcp_shutdown(conn, mbox,
N_("Cannot initialize SSL method."),
FALSE);
if ((conn->ssl_ctx = SSL_CTX_new(ssl_method)) == NULL)
return tcp_shutdown(conn, mbox,
N_("Cannot initialize SSL server certificate handler."),
FALSE);
SSL_CTX_set_options(conn->ssl_ctx, SSL_OP_ALL);
SSL_CTX_set_verify(conn->ssl_ctx, SSL_VERIFY_NONE, NULL);
if ((conn->ssl = SSL_new(conn->ssl_ctx)) == NULL)
return tcp_shutdown(conn, mbox,
N_("Cannot initialize SSL handler."),
FALSE);
#ifndef HAVE_GNUTLS
SSL_clear(conn->ssl);
#endif
SSL_set_fd(conn->ssl, conn->fd);
SSL_set_connect_state(conn->ssl);
if (SSL_connect(conn->ssl) < 0)
return tcp_shutdown(conn, mbox, tcp_error_message[0], FALSE);
return TRUE;
}
#endif
static gboolean
tcp_connect(ConnInfo *conn, Mailbox *mbox)
{
MailAccount *account = mbox->account;
gchar buf[128];
memset(conn, 0, sizeof(*conn));
if (_GK.debug_level & DEBUG_MAIL)
{
format_remote_mbox_name(mbox, buf, sizeof(buf));
g_debug("tcp_connect: connecting to %s\n", buf);
}
conn->fd = gkrellm_connect_to(account->server, account->port);
if (conn->fd < 0)
return tcp_warn(mbox, tcp_error_message[0], FALSE);
#ifdef HAVE_SSL
if (account->use_ssl == SSL_TRANSPORT && !ssl_negotiate(conn, mbox))
return FALSE;
#endif
return TRUE;
}
extern void to64frombits(unsigned char *, const unsigned char *, int);
extern int from64tobits(char *, const char *, int);
static void
hmac_md5(unsigned char *password, size_t pass_len,
unsigned char *challenge, size_t chal_len,
unsigned char *response, size_t resp_len)
{
int i;
unsigned char ipad[64];
unsigned char opad[64];
unsigned char hash_passwd[16];
MD5_CTX ctx;
if (resp_len != 16)
return;
if (pass_len > sizeof(ipad))
{
MD5Init(&ctx);
MD5Update(&ctx, password, pass_len);
MD5Final(hash_passwd, &ctx);
password = hash_passwd;
pass_len = sizeof(hash_passwd);
}
memset(ipad, 0, sizeof(ipad));
memset(opad, 0, sizeof(opad));
memcpy(ipad, password, pass_len);
memcpy(opad, password, pass_len);
for (i = 0; i < 64; i++)
{
ipad[i] ^= 0x36;
opad[i] ^= 0x5c;
}
MD5Init(&ctx);
MD5Update(&ctx, ipad, sizeof(ipad));
MD5Update(&ctx, challenge, chal_len);
MD5Final(response, &ctx);
MD5Init(&ctx);
MD5Update(&ctx, opad, sizeof(opad));
MD5Update(&ctx, response, resp_len);
MD5Final(response, &ctx);
}
/* authenticate as per RFC2195 */
static int
do_cram_md5(ConnInfo *conn, char *command, Mailbox *mbox, char *strip)
{
MailAccount *account = mbox->account;
gint len;
gchar buf1[1024];
gchar msg_id[768];
gchar reply[1024];
gchar *respdata;
guchar response[16];
snprintf(buf1, sizeof(buf1), "%s CRAM-MD5\r\n", command);
server_command(conn, mbox, buf1);
/* From RFC2195:
* The data encoded in the first ready response contains an
* presumptively arbitrary string of random digits, a
* timestamp, and the * fully-qualified primary host name of
* the server. The syntax of the * unencoded form must
* correspond to that of an RFC 822 'msg-id' * [RFC822] as
* described in [POP3].
*/
if (!server_response(conn, mbox, "+ "))
return FALSE;
/* caller may specify a response prefix we should strip if present */
respdata = mbox->tcp_in->str;
len = strlen(respdata);
if (respdata[len - 1] == '\n')
respdata[--len] = '\0';
if (respdata[len - 1] == '\r')
respdata[--len] = '\0';
if (strip && strncmp(respdata, strip, strlen(strip)) == 0)
respdata += strlen(strip);
len = from64tobits(msg_id, respdata, sizeof(msg_id));
if (len < 0)
{
gkrellm_debug(DEBUG_MAIL, _("could not decode BASE64 challenge\n"));
return FALSE;
}
else if (len < sizeof(msg_id))
msg_id[len] = 0;
else
msg_id[sizeof(msg_id) - 1] = 0;
gkrellm_debug(DEBUG_MAIL, _("decoded as %s\n"), msg_id);
/* The client makes note of the data and then responds with a string
* consisting of the user name, a space, and a 'digest'. The latter is
* computed by applying the keyed MD5 algorithm from [KEYED-MD5] where
* the key is a shared secret and the digested text is the timestamp
* (including angle-brackets).
*/
hmac_md5((guchar *) account->password, strlen(account->password),
(guchar *) msg_id, strlen(msg_id), response, sizeof(response));
snprintf(reply, sizeof(reply),
"%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
account->username,
response[0], response[1], response[2], response[3],
response[4], response[5], response[6], response[7],
response[8], response[9], response[10], response[11],
response[12], response[13], response[14], response[15]);
to64frombits((guchar *) buf1, (guchar *) reply, strlen(reply));
len = strlen(buf1);
if (len + 3 > sizeof(buf1))
return FALSE;
strcpy(buf1 + len, "\r\n");
server_command(conn, mbox, buf1);
return TRUE;
}
#ifdef HAVE_NTLM
/* NTLM authentication */
static int
do_ntlm(ConnInfo *conn, char *command, Mailbox *mbox)
{
gint len;
gchar msgbuf[2048];
tSmbNtlmAuthRequest request;
tSmbNtlmAuthChallenge challenge;
tSmbNtlmAuthResponse response;
snprintf(msgbuf, sizeof(msgbuf), "%s NTLM\r\n", command);
server_command(conn, mbox, msgbuf);
if (!server_response(conn, mbox, "+ "))
return FALSE;
buildSmbNtlmAuthRequest(&request, mbox->account->username, NULL);
if (_GK.debug_level & DEBUG_MAIL)
dumpSmbNtlmAuthRequest(stdout, &request);
memset(msgbuf, 0, sizeof(msgbuf));
to64frombits((guchar *) msgbuf, (guchar *) &request, SmbLength(&request));
len = strlen(msgbuf);
if (len + 3 > sizeof(msgbuf))
return FALSE;
strcpy(msgbuf + len, "\r\n");
server_command(conn, mbox, msgbuf);
if (!server_response(conn, mbox, "+ "))
return FALSE;
len = from64tobits((char *)&challenge, mbox->tcp_in->str,
sizeof(challenge));
if (len < 0)
{
gkrellm_debug(DEBUG_MAIL, _("could not decode BASE64 challenge\n"));
return FALSE;
}
if (_GK.debug_level & DEBUG_MAIL)
dumpSmbNtlmAuthChallenge(stdout, &challenge);
buildSmbNtlmAuthResponse(&challenge, &response,
mbox->account->username,
mbox->account->password);
if (_GK.debug_level & DEBUG_MAIL)
dumpSmbNtlmAuthResponse(stdout, &response);
memset(msgbuf, 0, sizeof msgbuf);
to64frombits((guchar *)msgbuf, (guchar *) &response, SmbLength(&response));
len = strlen(msgbuf);
if (len + 3 > sizeof(msgbuf))
return FALSE;
strcpy(msgbuf + len, "\r\n");
server_command(conn, mbox, msgbuf);
return TRUE;
}
#endif // HAVE_NTLM
static gboolean
check_pop3(Mailbox *mbox)
{
MailAccount *account = mbox->account;
ConnInfo conn;
gchar line[256], buf[256];
gchar *challenge = NULL;
if (!tcp_connect(&conn, mbox))
return FALSE;
/* Is the machine we are connected to really a POP3 server?
*/
if (! server_response(&conn, mbox, "+OK"))
return tcp_shutdown(&conn, mbox, tcp_error_message[1], FALSE);
if (account->authmech == AUTH_APOP &&
(strlen(mbox->tcp_in->str) < 3 ||
(challenge = g_strdup(mbox->tcp_in->str + 3)) == NULL))
return tcp_shutdown(&conn, mbox, tcp_error_message[1], FALSE);
#ifdef HAVE_SSL
if (account->use_ssl == SSL_STARTTLS)
{
server_command(&conn, mbox, "STLS\r\n");
if (!server_response(&conn, mbox, "+OK"))
{
if (challenge)
g_free(challenge);
return tcp_shutdown(&conn, mbox,
N_("Bad response after STLS."),
FALSE);
}
if (!ssl_negotiate(&conn, mbox))
{
if (challenge)
g_free(challenge);
return FALSE;
}
}
#endif
if (account->authmech == AUTH_APOP)
{
static const gchar hex[] = "0123456789abcdef";
MD5_CTX ctx;
gint i;
gchar *key, *p;
guchar digest[16];
gchar ascii_digest[33];
if ((key = strchr(challenge, '<')) == NULL)
{
g_free(challenge);
return tcp_shutdown(&conn, mbox, tcp_error_message[6],
TRUE);
}
if ((p = strchr(key, '>')) == NULL)
{
g_free(challenge);
return tcp_shutdown(&conn, mbox, tcp_error_message[6],
TRUE);
}
*(p + 1) = '\0';
snprintf(line, sizeof(line), "%s%s", key, account->password);
g_free(challenge);
MD5Init(&ctx);
MD5Update(&ctx, line, strlen(line));
MD5Final(digest, &ctx);
for (i = 0; i < 16; i++)
{
ascii_digest[i + i] = hex[digest[i] >> 4];
ascii_digest[i + i + 1] = hex[digest[i] & 0x0f];
}
ascii_digest[i + i] = '\0';
snprintf(line, sizeof(line),
"APOP %s %s\r\n", account->username, ascii_digest);
server_command(&conn, mbox, line);
}
else if (account->authmech == AUTH_CRAM_MD5)
{
if (!do_cram_md5(&conn, "AUTH", mbox, NULL))
{
/* SASL cancellation of authentication */
server_command(&conn, mbox, "*\r\n");
return tcp_shutdown(&conn, mbox, tcp_error_message[7], TRUE);
}
}
#ifdef HAVE_NTLM
else if (account->authmech == AUTH_NTLM)
{
if (!do_ntlm(&conn, "AUTH", mbox))
{
/* SASL cancellation of authentication */
server_command(&conn, mbox, "*\r\n");
return tcp_shutdown(&conn, mbox, tcp_error_message[7], TRUE);
}
}
#endif // HAVE_NTLM
else /* AUTH_USER */
{
snprintf (line, sizeof (line), "USER %s\r\n", account->username);
server_command(&conn, mbox, line);
if (! server_response(&conn, mbox, "+OK"))
return tcp_shutdown(&conn, mbox, tcp_error_message[2], TRUE);
snprintf (line, sizeof (line), "PASS %s\r\n", account->password);
tcp_putline(&conn, line);
if (_GK.debug_level & DEBUG_MAIL)
{
hide_password(line, 5, account->password);
format_remote_mbox_name(mbox, buf, sizeof(buf));
g_debug("server_command( %s ):%s", buf, line);
}
}
if (! server_response(&conn, mbox, "+OK"))
return tcp_shutdown(&conn, mbox, tcp_error_message[3], TRUE);
server_command(&conn, mbox, "STAT\r\n");
if (! server_response(&conn, mbox, "+OK"))
return tcp_shutdown(&conn, mbox, tcp_error_message[4], FALSE);
sscanf(mbox->tcp_in->str, "+OK %d", &mbox->mail_count);
snprintf (line, sizeof (line), "UIDL %d\r\n", mbox->mail_count);
server_command(&conn, mbox, line);
if (! server_response(&conn, mbox, "+OK"))
mbox->new_mail_count = mbox->mail_count;
else
/* Set the new_mail_count only if the UIDL is changed to avoid
| re-reporting mail is new after MUA button has been clicked.
*/
if ( sscanf(mbox->tcp_in->str, "+OK %*d %127s", line) == 1
&& ( gkrellm_dup_string(&mbox->uidl, line)
|| unseen_is_new
)
)
mbox->new_mail_count = mbox->mail_count;
server_command(&conn, mbox, "QUIT\r\n");
tcp_close(&conn);
return TRUE;
}
static gchar *imap_strescape(const gchar *source)
{
const guchar *p; /* running pointer inside source */
gchar *dst; /* resulting string */
gchar *q; /* running pointer inside dst */
/* Each source byte needs at most two destination chars due to escaping */
dst = g_malloc (strlen(source) * 2 + 1);
p = (guchar *)source;
q = dst;
while (*p)
{
switch (*p)
{
case '\\':
*q++ = '\\';
*q++ = '\\';
break;
case '"':
*q++ = '\\';
*q++ = '"';
break;
default:
*q++ = *p; // copy
break;
} // switch
p++;
} // while
*q = 0; // final 0-byte
return dst;
}
static gboolean
check_imap(Mailbox *mbox)
{
MailAccount *account = mbox->account;
ConnInfo conn;
gint messages = 0;
gint unseen = 0;
gint seq = 0;
gchar line[256], *ss;
gchar buf[256];
gchar *user_escaped, *pass_escaped, *fold_escaped;
if (!tcp_connect(&conn, mbox))
return FALSE;
/* Is the machine we are connected to really a IMAP server?
*/
if (! server_response(&conn, mbox, "* OK"))
return tcp_shutdown(&conn, mbox, tcp_error_message[1], FALSE);
#ifdef HAVE_SSL
if (account->use_ssl == SSL_STARTTLS)
{
gkrellm_debug(DEBUG_MAIL, "check_imap: Issuing STARTTLS\n");
snprintf(line, sizeof(line), "a%03d STARTTLS\r\n", ++seq);
server_command(&conn, mbox, line);
snprintf(line, sizeof(line), "a%03d OK", seq);
if (!imap_completion_result(&conn, mbox, line))
return tcp_shutdown(&conn, mbox,
N_("Bad response after STARTTLS."),
TRUE);
if (!ssl_negotiate(&conn, mbox))
return FALSE;
gkrellm_debug(DEBUG_MAIL, "check_imap: STARTTLS successful\n");
}
#endif
/* For debugging purposes we ask for capabilities (helps debugging
authentication problems) */
if (_GK.debug_level & DEBUG_MAIL)
{
gkrellm_debug(DEBUG_MAIL, "check_imap: Asking for capabilities\n");
snprintf(line, sizeof(line), "a%03d CAPABILITY\r\n", ++seq);
server_command(&conn, mbox, line);
snprintf(line, sizeof(line), "a%03d OK", seq);
if (!imap_completion_result(&conn, mbox, line))
{
return tcp_shutdown(&conn, mbox, N_("Bad response after CAPABILITY."),
TRUE);
}
}
if (account->authmech == AUTH_CRAM_MD5)
{
gkrellm_debug(DEBUG_MAIL, "check_imap: Using CRAM-MD5 for authentication\n");
snprintf(line, sizeof(line), "a%03d AUTHENTICATE", ++seq);
if (!do_cram_md5(&conn, line, mbox, NULL))
{
/* SASL cancellation of authentication */
server_command(&conn, mbox, "*\r\n");
return tcp_shutdown(&conn, mbox, tcp_error_message[7], TRUE);
}
}
#ifdef HAVE_NTLM
else if (account->authmech == AUTH_NTLM)
{
gkrellm_debug(DEBUG_MAIL, "check_imap: Using NTLM for authentication\n");
snprintf(line, sizeof(line), "a%03d AUTHENTICATE", ++seq);
if (!do_ntlm(&conn, line, mbox))
{
/* SASL cancellation of authentication */
server_command(&conn, mbox, "*\r\n");
return tcp_shutdown(&conn, mbox, tcp_error_message[7], TRUE);
}
}
#endif // HAVE_NTLM
else /* AUTH_LOGIN */
{
gkrellm_debug(DEBUG_MAIL, "check_imap: Using plaintext LOGIN for authentication\n");
user_escaped = imap_strescape(account->username);
pass_escaped = imap_strescape(account->password);
snprintf(line, sizeof(line), "a%03d LOGIN \"%s\" \"%s\"\r\n",
++seq, user_escaped, pass_escaped);
tcp_putline(&conn, line);
if (_GK.debug_level & DEBUG_MAIL)
{
hide_password(line, 15 + strlen(user_escaped), pass_escaped);
format_remote_mbox_name(mbox, buf, sizeof(buf));
g_debug("server_command( %s ):%s", buf, line);
}
g_free(user_escaped);
g_free(pass_escaped);
}
snprintf(line, sizeof(line), "a%03d OK", seq);
if (! imap_completion_result(&conn, mbox, line))
return tcp_shutdown(&conn, mbox, tcp_error_message[2], TRUE);
/* I expect the only untagged response to STATUS will be "* STATUS ..."
*/
fold_escaped = imap_strescape(account->imapfolder);
snprintf(line, sizeof(line),
"a%03d STATUS \"%s\" (MESSAGES UNSEEN)\r\n",
++seq, fold_escaped);
g_free(fold_escaped);
server_command(&conn, mbox, line);
if (! server_response(&conn, mbox, "*"))
return tcp_shutdown(&conn, mbox, tcp_error_message[4], FALSE);
if ((ss = strstr(mbox->tcp_in->str, "MESSAGES")) == NULL)
{
if (strrchr(mbox->tcp_in->str, '}'))
{
if (! server_response(&conn, mbox, ""))
return tcp_shutdown(&conn, mbox, tcp_error_message[4], FALSE);
if ((ss = strstr(mbox->tcp_in->str, "MESSAGES")) == NULL)
return tcp_shutdown(&conn, mbox, tcp_error_message[4], FALSE);
}
else
return tcp_shutdown(&conn, mbox, tcp_error_message[4], FALSE);
}
if (sscanf(ss, "MESSAGES %d", &messages) == 1)
{
if ((ss = strstr(mbox->tcp_in->str, "UNSEEN")) != NULL)
sscanf(ss, "UNSEEN %d", &unseen);
gkrellm_debug(DEBUG_MAIL, "check_imap: messages: %d; unseen: %d\n",
messages, unseen);
}
mbox->mail_count = messages;
mbox->new_mail_count = unseen;
snprintf(line, sizeof(line), "a%03d OK", seq);
imap_completion_result(&conn, mbox, line);
snprintf(line, sizeof(line), "a%03d LOGOUT\r\n", ++seq);
server_command(&conn, mbox, line);
snprintf(line, sizeof(line), "a%03d OK", seq);
imap_completion_result(&conn, mbox, line);
tcp_close(&conn);
return TRUE;
}
static gboolean
mh_sequences_new_count(Mailbox *mbox)
{
FILE *f;
gchar buf[1024];
gchar *path, *tok;
gint n0, n1;
path = g_strconcat(mbox->account->path, G_DIR_SEPARATOR_S,
".mh_sequences", NULL);
f = g_fopen(path, "r");
g_free(path);
if (!f)
return FALSE;
have_mh_sequences = TRUE;
if (mh_seq_ignore)
{
fclose(f);
return FALSE;
}
while (fgets(buf, sizeof(buf), f))
{
/* Look for unseen sequence like "unseen: 4 7-9 23"
*/
if (strncmp(buf, "unseen:", 7))
continue;
tok = strtok(buf, " \t\n");
while ((tok = strtok(NULL, " \t\n")) != NULL)
{
if (sscanf(tok, "%d-%d", &n0, &n1) == 2)
mbox->new_mail_count += n1 - n0 + 1;
else
mbox->new_mail_count++;
}
break;
}
fclose(f);
return TRUE;
}
/* Sylpheed procmsg.h enums MSG_NEW as (1 << 0) and MSG_UNREAD as (1 << 1)
| And procmsg_write_flags() in Sylpheeds procmsg.c writes a mail record as
| a pair of ints with msgnum first followed by flags.
*/
#define SYLPHEED_MSG_NEW 1
#define SYLPHEED_MSG_UNREAD 2
#define SYLPHEED_MARK_VERSION 2
static gboolean
sylpheed_mark_new_count(Mailbox *mbox)
{
FILE *f = NULL;
gchar *path;
gint msgnum, flags, ver, mark_files = 0;
static const gchar *mark_file_names[] = {
".sylpheed_mark", ".claws_mark", NULL
};
const gchar **fname;
for (fname = mark_file_names; *fname; fname++) {
path = g_strconcat(mbox->account->path, G_DIR_SEPARATOR_S,
*fname, NULL);
f = g_fopen(path, "rb");
g_free(path);
if (f)
break;
}
if (!f)
return FALSE;
if ( fread(&ver, sizeof(ver), 1, f) == 1
&& SYLPHEED_MARK_VERSION == ver
)
{
while ( fread(&msgnum, sizeof(msgnum), 1, f) == 1
&& fread(&flags, sizeof(flags), 1, f) == 1
)
{
if ( (flags & SYLPHEED_MSG_NEW)
|| ((flags & SYLPHEED_MSG_UNREAD) && unseen_is_new)
)
mbox->new_mail_count += 1;
++mark_files;
}
if (mark_files < mbox->mail_count)
mbox->new_mail_count += mbox->mail_count - mark_files;
}
fclose(f);
return TRUE;
}
/* Check a mh directory for mail. The way that messages are marked as new
| depends on the MUA being using. Only .mh_sequences and .sylpheed_mark
| are currently checked, otherwise all mail found is considered new mail.
*/
static gboolean
check_mh_dir(Mailbox *mbox)
{
GDir *dir;
gchar *name;
mbox->mail_count = mbox->new_mail_count = 0;
if ((dir = g_dir_open(mbox->account->path, 0, NULL)) == NULL)
return FALSE;
while ((name = (gchar *) g_dir_read_name(dir)) != NULL)
{
/* Files starting with a digit are messages. */
if (isdigit((unsigned char)name[0]))
mbox->mail_count++;
}
g_dir_close(dir);
/* Some MH dir clients use .mh_sequences, others such as mutt or gnus
| do not. For mixed cases, it's a user option to ignore .mh_sequences.
| Sylpheed uses .sylpheed_mark.
*/
if ( !mh_sequences_new_count(mbox)
&& !sylpheed_mark_new_count(mbox)
)
mbox->new_mail_count = mbox->mail_count;
return TRUE;
}
/* A maildir has new, cur, and tmp subdirectories. Any file in new
| or cur that does not begin with a '.' is a mail message. It is
| suggested that messages begin with the output of time() (9 digits)
| but while mutt and qmail use this standard, procmail does not.
| maildir(5) says:
| It is a good idea for readers to skip all filenames in
| new and cur starting with a dot. Other than this,
| readers should not attempt to parse filenames.
| So check_maildir() simply looks for files in new and cur.
| But if unseen_is_new flag is set, look for ":2,*S" file suffix where
| the 'S' indicates the mail is seen.
| See http://cr.yp.to/proto/maildir.html
*/
static gboolean
check_maildir(Mailbox *mbox)
{
gchar path[256], *s;
gchar *name;
GDir *dir;
mbox->new_mail_count = 0;
snprintf(path, sizeof(path), "%s%cnew", mbox->account->path,
G_DIR_SEPARATOR);
if ((dir = g_dir_open(path, 0, NULL)) != NULL)
{
while ((name = (gchar *) g_dir_read_name(dir)) != NULL)
mbox->new_mail_count++;
g_dir_close(dir);
}
mbox->mail_count = mbox->new_mail_count;
snprintf(path, sizeof(path), "%s%ccur", mbox->account->path,
G_DIR_SEPARATOR);
if ((dir = g_dir_open(path, 0, NULL)) != NULL)
{
while ((name = (gchar *) g_dir_read_name(dir)) != NULL)
{
mbox->mail_count++;
if ( unseen_is_new
&& ( (s = strchr(name, ':')) == NULL
|| !strchr(s, 'S')
)
)
mbox->new_mail_count++;
}
g_dir_close(dir);
}
gkrellm_debug(DEBUG_MAIL, "check_maildir %s: total=%d old=%d new=%d\n",
mbox->account->path, mbox->mail_count, mbox->old_mail_count,
mbox->new_mail_count);
return TRUE;
}
/* Count total mail and old mail in a mailbox. Old mail can be read
| with a Status: R0, or can be accessed and not read with Status: O
| So, new mail will be the diff - note that unread mail is not
| necessarily new mail. According to stat() man page:
| st_atime is changed by mknod(), utime(), read(), write(), truncate()
| st_mtime is changed by mknod(), utime(), write()
| But, new mail arriving (writing mailbox) sets st_mtime while reading
| the mailbox (mail program reading) sets st_atime. So the test
| st_atime > st_mtime is testing if mbox has been read since last new mail.
| Mail readers may restore st_mtime after writting status.
| And Netscape mail does status with X-Mozilla-Status: xxxS
| where S is bitwise or of status flags:
| 1: read 2: replied 4: marked 8: deleted
|
| Evolution uses status with X-Evolution: 00000000-xxxx where xxxx status is
| a bitfield in hexadecimal (see enum _CamelMessageFlags in evolution/camel
| source) and most importantly CAMEL_MESSAGE_SEEN = 1<<4.
*/
/* test if buf is a status for standard mail, mozilla or evolution
*/
static gboolean
is_status(gchar *buf)
{
if (buf[0] != 'S' && buf[0] != 'X')
return FALSE;
if ( !strncmp(buf, "Status:", 7) /* Standard mail clients */
|| !strncmp(buf, "X-Mozilla-Status:", 17) /* Netscape */
|| !strncmp(buf, "X-Evolution:", 12) ) /* Mozilla */
return TRUE;
else
return FALSE;
}
static gboolean
status_is_old(gchar *buf)
{
gchar c;
guint tmp;
/* Standard mail clients
*/
if ( !strncmp(buf, "Status:", 7)
&& (strchr(buf, 'R') || (!unseen_is_new && strchr(buf, 'O')))
)
return TRUE;
/* Netscape
*/
if (!strncmp(buf, "X-Mozilla-Status:", 17))
{
c = buf[21];
if (c < '8') /* Not deleted */
c -= '0';
if (c >= '8' || (c & 0x1))
return TRUE;
}
/* Evolution
*/
if (!strncmp(buf, "X-Evolution:", 12))
{
sscanf(buf+22, "%04x", &tmp);
if (tmp & (1<<4))
return TRUE;
}
return FALSE;
}
/* test if a mail is marked as deleted
| Evolution uses status with X-Evolution: 00000000-xxxx where xxxx status is
| a bitfield in hexadecimal (see enum _CamelMessageFlags in evolution/camel source)
| and most importantly CAMEL_MESSAGE_DELETED = 1<<1.
*/
static gboolean
status_is_deleted(gchar *buf)
{
guint tmp;
/* Standard mail clients
if ( !strncmp(buf, "Status:", 7) )
*/
/* Netscape
if (!strncmp(buf, "X-Mozilla-Status:", 17))
*/
/* Evolution
*/
if (!strncmp(buf, "X-Evolution:", 12))
{
sscanf(buf+22, "%04x", &tmp);
if (tmp & (1<<1))
return TRUE;
/* Junk is not explicitly marked as deleted but is shown as if
| where in evolution
*/
if (tmp & (1<<7))
return TRUE;
}
return FALSE;
}
static gboolean
check_mbox(Mailbox *mbox)
{
FILE *f;
struct utimbuf ut;
struct stat s;
gchar buf[1024];
gchar mpart_sep[1024];
gint in_header = FALSE;
gint marked_read = FALSE;
gint is_multipart = FALSE;
gint content_len = 0;
if (g_stat(mbox->account->path, &s) != 0)
{
mbox->mail_count = mbox->old_mail_count = mbox->new_mail_count = 0;
mbox->last_mtime = 0;
mbox->last_size = 0;
gkrellm_debug(DEBUG_MAIL, "check_mbox can't stat(%s): %s\n",
mbox->account->path, g_strerror(errno));
return FALSE;
}
/* Message no-counting mode reports new mail based on mailbox
| modified time and size.
*/
if (count_mode == MSG_NO_COUNT)
{
if ( s.st_size > 0
&& s.st_size >= mbox->last_size
&& s.st_mtime >= s.st_atime
)
mbox->new_mail_count = TRUE;
else
mbox->new_mail_count = FALSE;
mbox->mail_count = (s.st_size > 0) ? 1 : 0; /* boolean, not used */
mbox->old_mail_count = 0; /* not used */
mbox->last_size = s.st_size;
mbox->last_mtime = s.st_mtime;
return TRUE;
}
/* If the mailboxes have been modified since last check, count
| the new/total messages.
*/
if ( s.st_mtime != mbox->last_mtime
|| s.st_size != mbox->last_size
|| force_mail_check
)
{
if ((f = g_fopen(mbox->account->path, "r")) == NULL)
{
gkrellm_debug(DEBUG_MAIL, "check_mbox can't fopen(%s): %s\n",
mbox->account->path, g_strerror(errno));
return FALSE;
}
mbox->mail_count = 0;
mbox->old_mail_count = 0;
while(fgets(buf, sizeof(buf), f))
{
if (is_multipart && !in_header)
{
/* Skip to last line of multipart mail */
if (strncmp(buf,mpart_sep,strlen(mpart_sep))==0)
is_multipart = FALSE;
}
else if (buf[0] == '\n')
{
in_header = FALSE;
mbox->is_internal = FALSE;
if (content_len > 0) { /* Then see if we can jump to the next message in sequence */
long this_pos, jump_to;
this_pos = ftell(f);
jump_to = content_len + this_pos + 1;
if (fseek(f, jump_to, SEEK_SET) != 0 ||
fgets(buf, sizeof(buf), f) == NULL ||
strncmp("From ", buf, 5))
{
fseek(f, this_pos, SEEK_SET);
}
else /* We have an apparently valid From line */
{
if (is_From_line(mbox, buf))
{
mbox->mail_count += 1;
in_header = TRUE;
marked_read = FALSE;
content_len = 0;
is_multipart = FALSE;
}
else
{
fseek(f, this_pos, SEEK_SET);
}
}
}
}
else if (is_From_line(mbox, buf))
{
mbox->mail_count += 1;
in_header = TRUE;
marked_read = FALSE;
content_len = 0;
}
else if (in_header && !strncmp(buf, "Content-Length: ", 16))
{
content_len = atoi(buf + 16);
}
else if (in_header && is_status(buf))
{
if (status_is_old(buf) && !marked_read)
{
mbox->old_mail_count += 1;
marked_read = TRUE;
}
if (status_is_deleted(buf))
{
if (marked_read)
mbox->old_mail_count -= 1;
mbox->mail_count -= 1;
}
}
else if (in_header && mbox->is_internal)
{
if (strncmp(buf, "From: Mail System Internal Data", 31) == 0)
{
in_header = FALSE;
mbox->mail_count -= 1;
mbox->is_internal = FALSE;
}
}
else if (in_header && is_multipart_mail(buf,mpart_sep))
{
is_multipart = TRUE;
}
}
fclose(f);
/* Restore the mbox stat times for other mail checking programs and
| so the (st_atime > st_mtime) animation override below will work.
*/
ut.actime = s.st_atime;
ut.modtime = s.st_mtime;
utime(mbox->account->path, &ut);
mbox->last_mtime = s.st_mtime;
mbox->last_size = s.st_size;
gkrellm_debug(DEBUG_MAIL, "check_mbox %s: total=%d old=%d\n",
mbox->account->path, mbox->mail_count, mbox->old_mail_count);
}
/* Set the animation state when new mail count changes, and override
| the animation to false if mbox has been accessed since last modify
| (A MUA has probably read the mbox).
*/
mbox->new_mail_count = mbox->mail_count - mbox->old_mail_count;
if (s.st_atime > s.st_mtime)
{
mbox->need_animation = FALSE;
mbox->prev_new_mail_count = mbox->new_mail_count;
}
return TRUE;
}
static GkrellmDecal *mail_text_decal;
static GkrellmDecal *mail_icon_decal;
static void
draw_mail_text_decal(gint new_mail_count, gint mail_count)
{
GkrellmTextstyle ts, ts_save;
gint x, w;
GkrellmPanel *p;
GkrellmDecal *d;
GkrellmStyle *style;
GkrellmMargin *m;
gchar buf[32], nbuf[16], tbuf[16];
p = mail;
d = mail_text_decal;
ts_save = d->text_style;
if (new_mail_count == MUTE_FLAG)
{
snprintf(buf, sizeof(buf), _("mute"));
ts = *gkrellm_meter_alt_textstyle(style_id); /* Use the alt color */
ts.font = d->text_style.font;
}
else
{
ts = d->text_style;
if (count_mode == MSG_NO_COUNT)
buf[0] = '\0';
else if (count_mode == MSG_NEW_COUNT)
{
if (new_mail_count == 0)
strcpy(buf, "-");
else
snprintf(buf, sizeof(buf), "%d", new_mail_count);
}
else /* MSG_NEW_TOTAL_COUNT */
{
if (new_mail_count == 0)
strcpy(nbuf, "-");
else
snprintf(nbuf, sizeof(nbuf), "%d", new_mail_count);
if (mail_count == 0)
strcpy(tbuf, "-");
else
snprintf(tbuf, sizeof(tbuf), "%d", mail_count);
snprintf(buf, sizeof(buf), "%s/%s", nbuf, tbuf);
}
}
w = gkrellm_gdk_string_width(ts.font, buf);
if (w > d->w)
{
ts.font = gkrellm_meter_alt_textstyle(style_id)->font;
w = gkrellm_gdk_string_width(ts.font, buf);
}
style = gkrellm_meter_style(style_id);
m = gkrellm_get_style_margins(style);
x = gkrellm_chart_width() * p->label->position / GKRELLM_LABEL_MAX;
x -= m->right + w / 2;
if (p->label->position >= 50)
x -= mail_icon_decal->w;
if (x > d->w - w)
x = d->w - w;
if (x < 0)
x = 0;
d->text_style = ts;
d->x_off = x;
gkrellm_draw_decal_text(p, d, buf, 0);
d->text_style = ts_save;
}
static void
mbox_set_animation_state(Mailbox *mbox)
{
if (mbox->new_mail_count != mbox->prev_new_mail_count)
mbox->need_animation =
(mbox->new_mail_count > mbox->prev_new_mail_count);
}
static void
update_krell_animation_frame(void)
{
/* Run the animation. Just go back and forth with a pause on
| frames 1 and full_scale - 1 (frames 0 and full_scale are cut off).
| Frame 0 is blank anyway, and frame full_scale is just not used.
*/
if (anim_pause-- <= 0)
{
if (anim_frame <= 1)
anim_dir = 1;
if (anim_frame >= KRELL(mail)->full_scale - 1)
anim_dir = -1;
anim_frame += anim_dir;
anim_frame %= KRELL(mail)->full_scale;
if (anim_frame == 1 || anim_frame == KRELL(mail)->full_scale - 1)
anim_pause = 4;
}
}
/* I popen the mail_user_agent so I can know if it is running and
| the mail_fetch so I can process fetchmail/flist/fetcho/... output.
| Reading the pipes need to not block so GKrellM won't freeze.
| So, I need a special non-blocking pipe line reading routine.
*/
static void
pipe_command(Mailproc *mp)
{
gchar **argv;
GError *err = NULL;
gboolean res;
if (mp->pipe >= 0) /* Still running? */
{
gkrellm_debug(DEBUG_MAIL, "mail pipe_command: <%s> still running.\n",
mp->command);
return;
}
if (!mp->command || *mp->command == '\0')
return;
g_shell_parse_argv(mp->command, NULL, &argv, NULL);
gkrellm_debug(DEBUG_MAIL, "mail pipe_command <%s>\n", mp->command);
mp->pipe = -1;
res = g_spawn_async_with_pipes(NULL, argv, NULL,
G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL,
NULL, NULL, NULL, NULL, &mp->pipe, NULL, &err);
if (!res && err)
{
gkrellm_message_dialog(NULL, err->message);
g_error_free(err);
}
if (mp->read_gstring)
mp->read_gstring = g_string_truncate(mp->read_gstring, 0);
else
mp->read_gstring = g_string_new("");
#if !defined(WIN32)
if (mp->pipe != -1)
fcntl(mp->pipe, F_SETFL, O_NONBLOCK);
#endif
g_strfreev(argv);
}
/* Accumulate non-blocking reads from a pipe into a gstring. Then
| try to read a single line from the gstring. If a line is read,
| return the count of chars in the line. But if no line can be read
| and the other end of the pipe has exited (pipe eof), return -1.
| Otherwise return 0 to indicate no line is available but the pipe
| producer is still alive.
| Why this trouble?: the stdio fgets() reading from a non-blocking
| stream is not guaranteed to return complete '\n' delimited lines.
*/
static gint
fgets_pipe(gchar *line, gint len, Mailproc *mp)
{
gchar buf[512];
gint n;
if (mp->pipe >= 0)
{
#if defined(WIN32)
DWORD bytesAvailable = 0;
HANDLE hPipe = (HANDLE)_get_osfhandle(mp->pipe);
// pipe is blocking on windows so check before ending up in a
// blocking call to read()
if (!PeekNamedPipe(hPipe, NULL, 0, NULL, &bytesAvailable, NULL))
{
DWORD err = GetLastError();
// this is no real error if the started mua was closed in the meantime
if (err == ERROR_BROKEN_PIPE)
mp->pipe = -1;
else if (_GK.debug_level & DEBUG_MAIL)
g_debug("fgets_pipe: PeekNamedPipe() FAILED, error was %lu\n", err);
return 0;
}
if (bytesAvailable == 0)
return 0;
n = read(mp->pipe, buf, min(sizeof(buf) - 1, bytesAvailable));
#else
n = read(mp->pipe, buf, sizeof(buf) - 1);
#endif
if (n <= 0)
{
if ((n == 0) || (errno != EINTR && errno != EAGAIN))
{
if (close(mp->pipe) < 0 && errno == EINTR)
close(mp->pipe);
mp->pipe = -1;
}
}
else
{
buf[n] = '\0';
mp->read_gstring = g_string_append(mp->read_gstring, buf);
}
}
if (gkrellm_getline_from_gstring(&mp->read_gstring, line, len))
return strlen(line);
if (mp->pipe < 0)
{
if (mp->read_gstring)
g_string_free(mp->read_gstring, TRUE);
mp->read_gstring = NULL;
return -1;
}
return 0;
}
static gboolean
mua_is_launched(void)
{
gchar buf[128];
gint n;
if (mail_user_agent.pipe == -1)
return FALSE;
while ((n = fgets_pipe(buf, sizeof(buf), &mail_user_agent)) > 0)
;
return (n < 0) ? FALSE : TRUE;
}
/* Read mail_fetch pipe and if fetch_check_only_mode, look for output
| of fetchmail -c or flist so I can report mail from these programs.
|
| eg. for MH mail, the nmh program flist -all (from the man page):
| /work/Mail has 5 in sequence unseen (private); out of 46
| inbox+ has 10 in sequence unseen ; out of 153
| junklist has 0 in sequence unseen ; out of 63
|
| For fetchmail, if no remote mail, I get:
| fetchmail: No mail for billw at mail.wt.net
| If remote mail i will get lines like:
| 1 message for billw at mail.wt.net (32743 octets).
| or, as reported by a user, there could be:
| 26 messages (25 seen) for billw at mail.wt.net
| If the remote mail is fetched, I get additional lines like:
| reading message 1 of 1 (32743 octets) .................... flushed
| Note: above 26 messages (25 seen) should show as 1/26 on panel.
|
| And fetchmail can't make up its mind. A user gets this with 5.2.0:
| fetchmail: 5.2.0 querying xx.yy.net (protocol POP3) at Thu, 20 Jan 2000...
| fetchmail: 2 messages for uuu at xx.yy.net (20509 octets).
*/
/* Since there is now internal POP3 and IMAP checking, this will not be
| used as much, but there is still flist, using fetchmail for ssl, etc.
| Anyway, here's the code to grok the above mess!
*/
static gboolean
parse_fetch_line(gchar *line, gint *msg, gint *seen)
{
gchar *s, *p, *buf;
gint n, n1, n2;
gint tok_count = 0;
gint state = 0,
seen_flag = FALSE,
unseen_flag = FALSE;
*msg = 0;
*seen = 0;
n1 = n2 = 0;
buf = g_strdup(line);
s = strtok(buf, " \t()\n");
/* Trap out badly set Fetch/check option.
*/
if (!s || !strcmp(s, "reading") || !strcmp(s, "skipping"))
{
g_free(buf);
return FALSE;
}
if (_GK.debug_level & DEBUG_MAIL)
g_debug(" parse[");
while (s)
{
if (++tok_count > 3 && state == 0) /* need a int within 3 tokens */
break;
n = strtol(s, &p, 0);
if (*p == ' ' || *p == '\t' || *p == '\0' || *p == '\n')
{ /* Have an integer, and not a x.y version number */
if (_GK.debug_level & DEBUG_MAIL)
g_debug("*<%s>,st=%d,", s, state);
if (state == 0)
n1 = n;
else if (state == 1)
n2 = n;
if (_GK.debug_level & DEBUG_MAIL)
g_debug("n1=%d,n2=%d state=%d*", n1, n2, state);
++state;
}
else if (!strcmp(s, "seen") || !strcmp(s, _("seen")))
seen_flag = TRUE;
else if (!strcmp(s, "unseen"))
unseen_flag = TRUE;
s = strtok(NULL, " \t()\n");
}
if (state > 1 && seen_flag) /* 26 messages (25 seen) ... */
{
*msg = n1;
*seen = n2;
}
else if (state > 1 && unseen_flag) /* /xxx has 5 in sequence unseen ... */
{
*msg = n2;
*seen = n2 - n1;
}
else if (state > 0) /* 1 message for billw at ... */
*msg = n1; /* or Fetchmail: 1 message for .... */
if (_GK.debug_level & DEBUG_MAIL)
g_debug("]snf=%d sunf=%d msg=%d seen=%d STATE=%d\n",
seen_flag, unseen_flag, *msg, *seen, state);
g_free(buf);
return TRUE;
}
static gint
compare_mailboxes(gconstpointer a, gconstpointer b)
{
gchar a_name[128];
gchar b_name[128];
format_remote_mbox_name((Mailbox *)a, a_name, sizeof(a_name));
format_remote_mbox_name((Mailbox *)b, b_name, sizeof(b_name));
return strcmp(a_name, b_name);
}
static void
make_fetch_tooltip(gchar *line, gint msg, gint seen)
{
Mailbox *mbox;
MailAccount *account;
GList *old_mbox_pointer;
gchar buf[64], *s;
mbox = g_new0(Mailbox, 1);
account = g_new0(MailAccount, 1);
mbox->account = account;
account->mboxtype = MBOX_FETCH_TOOLTIP;
if ((s = strstr(line, "sequence unseen")) != NULL) /* flist */
{
sscanf(line, "%63s", buf);
account->username = g_strdup(buf);
}
else if ((s = strstr(line, " for ")) != NULL) /* fetchmail */
{
sscanf(s + 5, "%63s", buf);
account->username = g_strdup(buf);
if ((s = strstr(line, " at ")) != NULL)
{
sscanf(s + 4, "%63s", buf);
account->server = g_strdup(buf);
}
if ((s = strstr(line, "(folder ")) != NULL)
{
sscanf(s + 8, "%63[^)]", buf);
account->imapfolder = g_strdup(buf);
}
}
else
{
free_mailbox(mbox);
return;
}
old_mbox_pointer = g_list_find_custom(mailbox_list, mbox,
(GCompareFunc) compare_mailboxes);
if (old_mbox_pointer)
{
free_mailbox(mbox);
mbox = (Mailbox *) old_mbox_pointer->data;
if (mbox->account->mboxtype == MBOX_FETCH_TOOLTIP)
{
mbox->mail_count = msg;
mbox->new_mail_count = msg - seen;
}
}
else
{
mbox->mail_count = msg;
mbox->new_mail_count = msg - seen;
mailbox_list = g_list_insert_sorted(mailbox_list, mbox,
(GCompareFunc)(compare_mailboxes));
}
}
/* Read output lines from the fetch/check program. If fetch_check_only_mode
| is set, parse the lines to try to read message counts so they can be
| reported ("fetchmail -c" or equiv should be configured). However, if
| the mode is not set, then just read lines and waste them because mail
| is presumably being downloaded into local mailboxes.
*/
static void
read_mail_fetch(void)
{
Mailproc *mp = (Mailproc *) mail_fetch->private;
gchar buf[128];
gint n, msg, seen;
while ((n = fgets_pipe(buf, sizeof(buf), mp)) > 0) /* non-blocking */
{
if (_GK.debug_level & DEBUG_MAIL)
g_debug("read_mail_fetch(%d): %s\n", fetch_check_only_mode, buf);
if (fetch_check_only_mode)
{
if (parse_fetch_line(buf, &msg, &seen))
make_fetch_tooltip(buf, msg, seen);
if (msg > 0)
{
mail_fetch->mail_count += msg;
mail_fetch->old_mail_count += seen;
}
}
}
/* When fetch program is done, flag not busy so new counts will be used.
*/
if (n < 0)
{
if (fetch_check_only_mode)
{
mail_fetch->new_mail_count =
mail_fetch->mail_count - mail_fetch->old_mail_count;
mbox_set_animation_state(mail_fetch);
}
mail_fetch->busy = FALSE;
}
}
static void
reset_mail_fetch(void)
{
Mailproc *mp = (Mailproc *) mail_fetch->private;
if (mp->pipe >= 0)
{
if (close(mp->pipe) < 0 && errno == EINTR)
close(mp->pipe);
}
mp->pipe = -1;
if (mp->read_gstring)
g_string_free(mp->read_gstring, TRUE);
mp->read_gstring = NULL;
mail_fetch->mail_count = 0;
mail_fetch->new_mail_count = 0;
mail_fetch->old_mail_count = 0;
mail_fetch->need_animation = FALSE;
mail_fetch->busy = FALSE;
force_mail_check = TRUE;
}
static gboolean
run_fetch_program(void)
{
Mailbox *mbox;
Mailproc *mp = (Mailproc *) mail_fetch->private;
GList *list;
if ( !mail_fetch->busy && !mp->read_gstring
&& mp->command && *(mp->command) != '\0'
)
{
mail_fetch->busy = TRUE;
for (list = mailbox_list; list; list = list->next)
{
mbox = (Mailbox *) list->data;
if ( mbox->account->mboxtype == MBOX_FETCH
|| mbox->account->mboxtype == MBOX_FETCH_TOOLTIP
)
{
mbox->mail_count = 0;
mbox->new_mail_count = 0;
mbox->old_mail_count = 0;
}
}
pipe_command(mp);
if (mp->pipe >= 0)
return TRUE;
}
return FALSE;
}
static gpointer
mail_check_thread(void *data)
{
Mailbox *mbox = (Mailbox *) data;
gchar buf[256];
gint external;
buf[0] = '\0';
if (_GK.debug_level & DEBUG_MAIL)
{
format_remote_mbox_name(mbox, buf, sizeof(buf));
g_debug("Start mail_check_thread: %s at %d:%d:%d\n", buf,
gkrellm_get_current_time()->tm_hour,
gkrellm_get_current_time()->tm_min,
gkrellm_get_current_time()->tm_sec);
}
external = mbox->account->mboxtype & MBOX_EXTERNAL;
if ( (*(mbox->check_func))(external ? mbox->data : mbox) == FALSE)
{
mbox->mail_count = 0;
mbox->new_mail_count = 0;
mbox->need_animation = FALSE;
}
else
mbox_set_animation_state(mbox);
gkrellm_debug(DEBUG_MAIL, "Stop mail_check_thread: %s at %d:%d:%d\n", buf,
gkrellm_get_current_time()->tm_hour,
gkrellm_get_current_time()->tm_min,
gkrellm_get_current_time()->tm_sec);
mbox->busy = FALSE;
// mbox->thread = NULL;
return NULL;
}
static void
update_mail(void)
{
Mailbox *mbox;
GList *list;
gint external, prev_new_mail_count, any_busy;
gint local_check = FALSE,
remote_check = FALSE,
fetch_check;
static gint second_count,
minute_count,
sound_inhibit;
if (!enable_mail || !mailbox_list)
return;
if (GK.minute_tick && (++minute_count % remote_check_timeout) == 0)
remote_check = TRUE;
if (GK.second_tick && (++second_count % local_check_timeout) == 0)
local_check = TRUE;
fetch_check = fetch_check_is_local ? local_check : remote_check;
if (remote_check || local_check)
mua_is_launched(); /* update pipe, avoid lingering zombie */
if ( force_mail_check
|| ( GK.second_tick
&& !(mute_mode && super_mute_mode)
&& !(mua_inhibit_mode && mail_user_agent.pipe >= 0)
)
)
{
prev_new_mail_count = new_mail_count;
total_mail_count = 0;
new_mail_count = 0;
run_animation = FALSE;
remote_check |= force_mail_check;
local_check |= force_mail_check;
fetch_check |= force_mail_check;
if (force_mail_check)
minute_count = second_count = 0;
any_busy = FALSE;
for (list = mailbox_list; list; list = list->next)
{
mbox = (Mailbox *) list->data;
external = mbox->account->mboxtype & MBOX_EXTERNAL;
switch (mbox->account->mboxtype & MBOX_CHECK_TYPE_MASK)
{
case MBOX_CHECK_FETCH:
if (((Mailproc *)(mail_fetch->private))->read_gstring)
read_mail_fetch();
if (fetch_check)
(*mbox->check_func)(mbox);
break;
case MBOX_CHECK_INLINE: /* Local mailbox or maildir check */
if (local_check)
{
if (mbox->check_func)
(*mbox->check_func)(external ? mbox->data : mbox);
mbox_set_animation_state(mbox);
}
break;
case MBOX_CHECK_THREADED:
if (remote_check && !mbox->busy && mbox->check_func)
{
if (mbox->thread)
g_thread_unref(mbox->thread);
mbox->busy = TRUE;
mbox->thread = g_thread_new("mail_check",
mail_check_thread, mbox);
}
else if ( (_GK.debug_level & DEBUG_MAIL)
&& remote_check && mbox->busy
)
g_debug(" %s thread busy\n", mbox->account->server);
break;
default: /* Unknown or pseudo mail box type */
continue;
}
/* If a mailbox check is busy (thread or remote fetch program
| running), use previous counts until the check is done.
*/
if (mbox->busy)
{
total_mail_count += mbox->prev_mail_count;
new_mail_count += mbox->prev_new_mail_count;
run_animation |= mbox->prev_need_animation;
any_busy = TRUE;
}
else
{
total_mail_count += mbox->mail_count;
new_mail_count += mbox->new_mail_count;
if (mbox->new_mail_count && cont_animation_mode)
mbox->need_animation = TRUE;
run_animation |= mbox->need_animation;
mbox->prev_mail_count = mbox->mail_count;
mbox->prev_new_mail_count = mbox->new_mail_count;
mbox->prev_need_animation = mbox->need_animation;
if (mbox->warn_msg && !mbox->warned)
{
gkrellm_message_dialog(NULL, mbox->warn_msg);
mbox->warned = TRUE;
}
}
}
force_mail_check = FALSE;
if ((_GK.debug_level & DEBUG_MAIL) && (local_check || remote_check))
g_debug("Mail check totals: total=%d new=%d anim=%d [%d,%d,%d]\n",
total_mail_count, new_mail_count, run_animation,
local_check, remote_check, fetch_check);
// Update tooltip if mail count changed or no tooltip has been set yet
if ( prev_new_mail_count != new_mail_count
#if GTK_CHECK_VERSION(2,12,0)
|| gtk_widget_get_has_tooltip(mail->drawing_area) == FALSE
#else
|| tooltip->active_tips_data == NULL
#endif
)
{
update_tooltip();
}
/* Run the notify (sound) command if the new mail count goes up,
| and if the various modes permit. Ensure a sound command is
| issued only once per remote check interval by locking it out
| if a new mail count triggers the sound and there are other
| remote threads still busy.
*/
if ( !sound_inhibit && !mute_mode
&& new_mail_count > prev_new_mail_count
&& mail_notify && mail_notify[0] != '\0'
)
{
g_spawn_command_line_async(mail_notify, NULL /* GError */);
if (any_busy)
sound_inhibit = TRUE;
}
if (!any_busy)
sound_inhibit = FALSE;
}
if (mute_mode && (GK.timer_ticks % 15) < 3) /* Asymmetric blink */
draw_mail_text_decal(MUTE_FLAG, MUTE_FLAG);
else
draw_mail_text_decal(new_mail_count, total_mail_count);
if (run_animation && (animation_mode & ANIMATION_PENGUIN))
update_krell_animation_frame();
else
anim_frame = 0;
if ((GK.timer_ticks % _GK.decal_mail_delay) == 0)
{
if (run_animation)
{
if (animation_mode & ANIMATION_ENVELOPE)
{
++decal_frame;
if (decal_frame >= _GK.decal_mail_frames)
decal_frame = 1;
}
else
decal_frame = 0;
}
else
decal_frame = 1;
}
gkrellm_draw_decal_pixmap(mail, DECAL(mail), decal_frame);
/* All the animation frame drawing is done with the general krell code.
*/
KRELL(mail)->previous = 0;
gkrellm_update_krell(mail, KRELL(mail), anim_frame);
gkrellm_draw_panel_layers(mail);
}
static gint
mail_expose_event(GtkWidget *widget, GdkEventExpose *ev)
{
if (widget == mail->drawing_area)
gdk_draw_drawable(widget->window, gkrellm_draw_GC(1), mail->pixmap,
ev->area.x, ev->area.y, ev->area.x, ev->area.y,
ev->area.width, ev->area.height);
return FALSE;
}
/* The mua launch button also optionally stops animations and resets
| remote mail counts. So this routine decides if it should be enabled.
*/
static void
set_mua_button_sensitivity(void)
{
if ( *(mail_user_agent.command) || reset_remote_mode
|| (animation_mode && !cont_animation_mode)
)
gkrellm_set_button_sensitive(mua_button, TRUE);
else
gkrellm_set_button_sensitive(mua_button, FALSE);
}
/* Callback for the message count decal button.
*/
static void
cb_mail_button(GkrellmDecalbutton *button)
{
GList *list;
Mailbox *mbox;
GError *err = NULL;
gboolean res;
if (reset_remote_mode)
{
for (list = mailbox_list; list; list = list->next)
{
mbox = (Mailbox *) list->data;
if ( (mbox->account->mboxtype & MBOX_CHECK_THREADED)
|| (mbox->account->mboxtype & MBOX_CHECK_FETCH)
|| mbox->account->mboxtype == MBOX_FETCH_TOOLTIP
)
{
GDK_THREADS_ENTER();
mbox->mail_count = 0;
mbox->new_mail_count = 0;
mbox->old_mail_count = 0;
mbox->need_animation = FALSE;
GDK_THREADS_LEAVE();
}
}
}
if (!cont_animation_mode)
{
for (list = mailbox_list; list; list = list->next)
{
mbox = (Mailbox *) list->data;
mbox->need_animation = FALSE;
}
run_animation = FALSE;
}
if (enable_multimua)
{
if (mail_user_agent.command && *mail_user_agent.command != '\0')
{
res = g_spawn_command_line_async(mail_user_agent.command, &err);
if (!res && err)
{
gkrellm_message_dialog(NULL, err->message);
g_error_free(err);
}
}
}
else if (!mua_is_launched())
{
pipe_command(&mail_user_agent);
}
else
{
check_timeout = 0;
gkrellm_debug(DEBUG_MAIL, "Mail user agent is already running.\n");
}
}
/* Callback for any button clicks on the Mail panel. Must exclude
| button 1 clicks on the message decal button.
*/
static gint
cb_panel_press(GtkWidget *widget, GdkEventButton *ev)
{
GkrellmDecal *d = mail_icon_decal;
if (ev->button == 3)
gkrellm_open_config_window(mon_mail);
/* button 2 anywhere in the panel toggles the mute mode.
*/
if (ev->button == 2)
{
mute_mode = !mute_mode;
/* If coming out of mute mode and mail checking was inhibited,
| force a check.
*/
if ( ! mute_mode && super_mute_mode
&& (! mua_is_launched() || ! mua_inhibit_mode)
)
force_mail_check = TRUE;
}
/* Button 1 press on the envelope - must exclude the message count button.
*/
if (ev->button == 1 && ev->x >= d->x && ev->x < d->x + d->w)
force_mail_check = TRUE;
return FALSE;
}
static void
dup_account(MailAccount *dst, MailAccount *src)
{
dst->path = g_strdup(src->path);
dst->homedir_path = g_strdup(src->homedir_path);
dst->server = g_strdup(src->server);
dst->username = g_strdup(src->username);
dst->password = g_strdup(src->password);
dst->imapfolder = g_strdup(src->imapfolder);
dst->mboxtype = src->mboxtype;
dst->protocol = src->protocol;
dst->authmech = src->authmech;
dst->port = src->port;
dst->use_ssl = src->use_ssl;
}
static gboolean
get_local_mboxtype(MailAccount *account)
{
gchar *path;
if (!account->path)
return FALSE;
if (*(account->path) == '~')
{
account->homedir_path = account->path;
account->path = g_strdup_printf("%s%s", gkrellm_homedir(),
account->homedir_path + 1);
}
if (g_file_test(account->path, G_FILE_TEST_IS_DIR))
{
path = g_build_path(G_DIR_SEPARATOR_S, account->path, "new", NULL);
if (g_file_test(path, G_FILE_TEST_IS_DIR))
account->mboxtype = MBOX_MAILDIR;
else
account->mboxtype = MBOX_MH_DIR;
g_free(path);
}
else
account->mboxtype = MBOX_MBOX;
return TRUE;
}
static Mailbox *
add_mailbox(MailAccount *account)
{
Mailbox *mbox;
if (!account)
return NULL;
mbox = g_new0(Mailbox, 1);
mbox->account = account;
if (account->path)
{
if (*(account->path) == '~')
{
account->homedir_path = account->path;
account->path = g_strdup_printf("%s%s", gkrellm_homedir(),
account->homedir_path + 1);
}
if (account->mboxtype == MBOX_MAILDIR)
mbox->check_func = check_maildir;
else if (account->mboxtype == MBOX_MH_DIR)
{
mbox->check_func = check_mh_dir;
checking_mh_mail = TRUE;
}
else
mbox->check_func = check_mbox;
}
else if (account->mboxtype == MBOX_REMOTE)
{
switch (account->protocol)
{
case PROTO_POP3:
mbox->check_func = check_pop3;
break;
case PROTO_IMAP:
mbox->check_func = check_imap;
break;
default:
g_free(mbox);
mbox = NULL;
}
}
else
{
g_free(mbox);
mbox = NULL;
}
if (mbox)
mailbox_list = g_list_append(mailbox_list, mbox);
return mbox;
}
/* Pre 1.2.7 mailbox config parsing.
*/
static Mailbox *
old_add_mailbox(gchar *arg)
{
Mailbox *mbox;
MailAccount *account;
gchar parms[4][CFG_BUFSIZE];
gint n;
gchar *p;
account = g_new0(MailAccount, 1);
n = sscanf(arg, "%s %s %s %s", parms[0], parms[1], parms[2], parms[3]);
if (n == 1)
{
account->path = g_strdup(parms[0]);
get_local_mboxtype(account);
mbox = add_mailbox(account);
return mbox;
}
mbox = g_new0(Mailbox, 1);
mbox->account = account;
switch (n)
{
case 3:
account->mboxtype = MBOX_REMOTE;
if ((p = strchr(parms[0], ':')) != NULL)
*p++ = '\0';
account->server = g_strdup(parms[0]);
account->port = (p && *p) ? atoi(p) : atoi(DEFAULT_POP3_PORT);
account->protocol = PROTO_POP3;
if ((p = strchr(parms[1], '/')) != NULL)
{
*p++ = '\0';
if (strcasecmp(p, "user") == 0)
account->authmech = AUTH_USER;
else if (strcasecmp(p, "apop") == 0)
account->authmech = AUTH_APOP;
else
{
g_free(account);
g_free(mbox);
return NULL;
}
}
else
account->authmech = AUTH_USER;
account->username = g_strdup(parms[1]);
account->password = g_strdup(parms[2]);
account->use_ssl = SSL_NONE;
mbox->check_func = check_pop3;
break;
case 4:
account->mboxtype = MBOX_REMOTE;
if ((p = strchr(parms[0], ':')) != NULL)
*p++ = '\0';
account->server = g_strdup(parms[0]);
account->port = (p && *p) ? atoi(p) : atoi(DEFAULT_IMAP_PORT);
account->imapfolder = g_strdup(parms[1]);
account->username = g_strdup(parms[2]);
account->password = g_strdup(parms[3]);
account->protocol = PROTO_IMAP;
account->authmech = AUTH_LOGIN;
account->use_ssl = SSL_NONE;
mbox->check_func = check_imap;
break;
default: /* Invalid mailbox line */
g_free(account);
g_free(mbox);
return NULL;
}
mailbox_list = g_list_append(mailbox_list, mbox);
return mbox;
}
static void
create_mail(GtkWidget *vbox, gint first_create)
{
GkrellmStyle *style;
GkrellmMargin *m;
GkrellmPanel *p;
MailAccount *account;
Mailproc *mp = (Mailproc*) mail_fetch->private;
gchar *s, buf[64];
gint x, w;
static GkrellmPiximage *krell_penguin_piximage;
if (first_create)
mail = gkrellm_panel_new0();
else
{
gkrellm_destroy_decal_list(mail);
gkrellm_destroy_krell_list(mail);
}
p = mail;
/* Force create user local mailbox if no mailchecking has been configured.
*/
if (!mailbox_list->next && local_supported && !*mp->command)
{ /* First mailbox is internal fetch */
if ((s = getenv("MAIL")) == NULL)
if ((s = getenv("USER")) != NULL)
{
if (g_file_test("/var/mail", G_FILE_TEST_IS_DIR))
snprintf(buf, sizeof(buf), "/var/mail/%s", s);
else
snprintf(buf, sizeof(buf), "/var/spool/mail/%s", s);
s = buf;
}
if (s)
{
account = g_new0(MailAccount, 1);
account->path = g_strdup(s);
account->mboxtype = MBOX_MBOX;
add_mailbox(account);
}
}
style = gkrellm_meter_style(style_id);
m = gkrellm_get_style_margins(style);
if (krell_penguin_piximage)
{
gkrellm_destroy_piximage(krell_penguin_piximage);
krell_penguin_piximage = NULL;
}
gkrellm_load_piximage("krell_penguin", NULL, &krell_penguin_piximage,
MAIL_STYLE_NAME);
gkrellm_create_krell(p,
krell_penguin_piximage ? krell_penguin_piximage
: gkrellm_krell_meter_piximage(style_id),
style);
KRELL(p)->full_scale = style->krell_depth - 1;
gkrellm_load_piximage("decal_mail", decal_mail_xpm, &decal_mail_piximage,
MAIL_STYLE_NAME);
mail_icon_decal = gkrellm_make_scaled_decal_pixmap(p, decal_mail_piximage,
style, _GK.decal_mail_frames, -1, -1, 0, 0);
/* The new/total mail text needs to be a decal because the text changes
| and must be drawn as a layer in update_layers().
| Calculate x,w override values. Cannot override w after the create.
*/
x = m->left;
if (style->label_position >= 50)
x += mail_icon_decal->w;
w = gkrellm_chart_width() - mail_icon_decal->w - m->left - m->right;
mail_text_decal = gkrellm_create_decal_text(p, "0",
gkrellm_meter_textstyle(style_id),
style, x, -1, w); /* -1 means use y default */
gkrellm_panel_configure(p, NULL, style);
gkrellm_panel_create(vbox, mon_mail, p);
/* Center the decals with respect to each other.
*/
if (mail_icon_decal->h > mail_text_decal->h)
mail_text_decal->y += (mail_icon_decal->h - mail_text_decal->h) / 2;
else
mail_icon_decal->y += (mail_text_decal->h - mail_icon_decal->h) / 2;
mua_button = gkrellm_put_decal_in_meter_button(p, mail_text_decal,
cb_mail_button, NULL, NULL);
set_mua_button_sensitivity();
if(!enable_mail)
{
gkrellm_panel_hide(p);
gkrellm_spacers_hide(mon_mail);
}
else
gkrellm_spacers_show(mon_mail);
if (first_create)
{
g_signal_connect(G_OBJECT(p->drawing_area), "expose_event",
G_CALLBACK(mail_expose_event), NULL);
g_signal_connect(G_OBJECT(p->drawing_area),"button_press_event",
G_CALLBACK(cb_panel_press), NULL);
#if !GTK_CHECK_VERSION(2,12,0)
tooltip = gtk_tooltips_new();
#endif
}
}
static AuthType *
authtype_from_string(gchar *s)
{
gint t;
for (t = 0; auth_strings[t].string != NULL; ++t)
if (!strcmp(s, auth_strings[t].string))
break;
return &auth_strings[t];
}
static gint
menu_authtype(gint proto, gint mech)
{
gint t;
for (t = 0; auth_strings[t].string != NULL; ++t)
if (auth_strings[t].protocol == proto &&
auth_strings[t].authmech == mech)
break;
return t;
}
#define menu_to_proto(type) auth_strings[type].protocol
#define menu_to_mech(type) auth_strings[type].authmech
#define auth_string(proto, mech) \
auth_strings[menu_authtype(proto, mech)].string
static void
save_mail_config(FILE *f)
{
Mailbox *mbox;
MailAccount *account;
Mailproc *mp = (Mailproc*) mail_fetch->private;
GList *list;
gchar *pwd, *qq;
for (list = mailbox_list; list; list = list->next)
{
mbox = (Mailbox *) list->data;
account = mbox->account;
switch (account->mboxtype)
{
case MBOX_MBOX:
case MBOX_MAILDIR:
case MBOX_MH_DIR:
fprintf(f, "mail mailbox-local %s %s\n",
mbox_strings[account->mboxtype & 0xf],
account->homedir_path ?
account->homedir_path : account->path);
break;
case MBOX_REMOTE:
pwd = account->password;
if ((qq = strchr(pwd, '"')) != NULL)
pwd = "password";
fprintf(f, "mail mailbox-remote %s %s \"%s\" \"%s\" %d",
auth_string(account->protocol,
account->authmech),
account->server, account->username,
account->password, account->port);
if (account->protocol == PROTO_IMAP)
fprintf(f, " \"%s\"",
account->imapfolder);
fprintf(f, "\n");
if (qq)
fprintf(f, "mail password %s\n", account->password);
#ifdef HAVE_SSL
fprintf(f, "mail mailbox-remote-use-ssl %d\n",
account->use_ssl);
#endif
break;
default:
break;
}
}
fprintf(f, "mail mua %s\n", mail_user_agent.command);
fprintf(f, "mail notify %s\n", mail_notify);
fprintf(f, "mail fetch_command %s\n", mp->command);
fprintf(f, "mail remote_check_timeout %d\n", remote_check_timeout);
fprintf(f, "mail local_check_timeout %d\n", local_check_timeout);
fprintf(f, "mail fetch_check_is_local %d\n", fetch_check_is_local);
fprintf(f, "mail msg_count_mode %d\n", count_mode);
fprintf(f, "mail animation_select_mode %d\n", animation_mode);
fprintf(f, "mail fetch_check_only_mode %d\n", fetch_check_only_mode);
fprintf(f, "mail reset_remote_mode %d\n", reset_remote_mode);
fprintf(f, "mail unseen_is_new %d\n", unseen_is_new);
fprintf(f, "mail enable %d %d %d %d\n", enable_mail, super_mute_mode,
mua_inhibit_mode, enable_multimua);
fprintf(f, "mail animation_continuous %d\n", cont_animation_mode);
fprintf(f, "mail show_tooltip %d\n", show_tooltip);
fprintf(f, "mail mh_seq_ignore %d\n", mh_seq_ignore);
}
static void
load_mail_config(gchar *arg)
{
static MailAccount *account_prev;
MailAccount *account = NULL;
Mailproc *mp = (Mailproc*) mail_fetch->private;
gchar mail_config[32], item[CFG_BUFSIZE], path[256];
gchar *str, *s;
gint n;
AuthType *authtype;
n = sscanf(arg, "%31s %[^\n]", mail_config, item);
if (n == 2)
{
if ( (_GK.debug_level & DEBUG_MAIL)
&& strcmp(mail_config, "password")
&& strcmp(mail_config, "mailbox-remote") /* avoid password */
)
g_debug("%s %s\n", mail_config, item);
if (!strcmp(mail_config, "mailbox")) /* Old config, pre 1.2.7 */
old_add_mailbox(item);
else if (!strcmp(mail_config, "mailbox-local"))
{
if (local_supported && sscanf(item, "%*s %255[^\n]", path) == 1)
{
account = g_new0(MailAccount, 1);
account->path = g_strdup(path);
get_local_mboxtype(account);
add_mailbox(account);
}
}
else if (!strcmp(mail_config, "mailbox-remote"))
{
account = g_new0(MailAccount, 1);
account_prev = NULL;
account->mboxtype = MBOX_REMOTE;
str = item;
s = gkrellm_dup_token(&str, NULL);
authtype = authtype_from_string(s);
account->protocol = authtype->protocol;
account->authmech = authtype->authmech;
g_free(s);
account->server = gkrellm_dup_token(&str, NULL);
account->username = gkrellm_dup_token(&str, NULL);
account->password = gkrellm_dup_token(&str, NULL);
s = gkrellm_dup_token(&str, NULL);
account->port = atoi(s);
g_free(s);
account->imapfolder = gkrellm_dup_token(&str, NULL);;
if (add_mailbox(account))
account_prev = account; /* XXX */
else
{
free_account(account);
account_prev = NULL;
}
}
#ifdef HAVE_SSL
else if (account_prev &&
!strcmp(mail_config, "mailbox-remote-use-ssl"))
sscanf(item, "%d", &account_prev->use_ssl);
#endif
else if (account_prev && !strcmp(mail_config, "password"))
gkrellm_dup_string(&account_prev->password, item);
else if (strcmp(mail_config, "mua") == 0)
mail_user_agent.command = g_strdup(item);
else if (strcmp(mail_config, "notify") == 0)
mail_notify = g_strdup(item);
else if (strcmp(mail_config, "fetch_command") == 0)
mp->command = g_strdup(item);
else if (strcmp(mail_config, "remote_check_timeout") == 0)
sscanf(item, "%d", &remote_check_timeout);
else if (strcmp(mail_config, "fetch_timeout") == 0) /* pre 2.2.5 */
sscanf(item, "%d", &remote_check_timeout);
else if (strcmp(mail_config, "local_check_timeout") == 0)
sscanf(item, "%d", &local_check_timeout);
else if (strcmp(mail_config, "check_timeout") == 0) /* pre 2.2.5 */
sscanf(item, "%d", &local_check_timeout);
else if (strcmp(mail_config, "fetch_check_is_local") == 0)
sscanf(item, "%d", &fetch_check_is_local);
else if (strcmp(mail_config, "msg_count_mode") == 0)
sscanf(item, "%d", &count_mode);
else if (strcmp(mail_config, "animation_select_mode") == 0)
sscanf(item, "%d", &animation_mode);
else if (strcmp(mail_config, "fetch_check_only_mode") == 0)
sscanf(item, "%d", &fetch_check_only_mode);
else if (strcmp(mail_config, "reset_remote_mode") == 0)
sscanf(item, "%d", &reset_remote_mode);
else if (strcmp(mail_config, "unseen_is_new") == 0)
sscanf(item, "%d", &unseen_is_new);
else if (strcmp(mail_config, "enable") == 0)
sscanf(item, "%d %d %d %d", &enable_mail, &super_mute_mode,
&mua_inhibit_mode, &enable_multimua);
else if (strcmp(mail_config, "animation_continuous") == 0)
sscanf(item, "%d", &cont_animation_mode);
else if (strcmp(mail_config, "show_tooltip") == 0)
sscanf(item, "%d", &show_tooltip);
else if (strcmp(mail_config, "mh_seq_ignore") == 0)
sscanf(item, "%d", &mh_seq_ignore);
}
}
/* ---------------------------------------------------------------------*/
enum
{
PROTOCOL_COLUMN,
#ifdef HAVE_SSL
SSL_COLUMN,
#endif
MAILBOX_COLUMN,
ACCOUNT_COLUMN,
N_COLUMNS
};
static GtkTreeView *treeview;
static GtkTreeRowReference *row_reference;
static GtkTreeSelection *selection;
static GtkWidget *account_notebook;
static GtkWidget *mbox_path_entry;
static GtkWidget *server_entry,
*user_entry,
*password_entry,
*imapfolder_entry,
*port_entry,
#ifdef HAVE_SSL
*ssl_combo_box,
#endif
*port_button;
static GtkWidget *local_button,
*remote_button,
*delete_button,
*new_apply_button,
*remote_combo_box;
static GtkWidget *mail_user_agent_entry;
static GtkWidget *enable_multimua_button;
static GtkWidget *mail_fetch_entry;
static GtkWidget *mail_notify_entry;
static GtkWidget *enable_cont_anim_button;
static GtkWidget *super_mute_button;
static GtkWidget *count_mode_button[3];
static GtkWidget *anim_button[4];
static GtkWidget *check_only_button;
static GtkWidget *reset_remote_button;
static GtkWidget *unseen_is_new_button;
static GtkWidget *mua_inhibit_button;
static GtkWidget *show_tooltip_button;
static GtkWidget *mh_seq_ignore_button;
static GList *config_mailbox_list;
static gint optmenu_auth_protocol;
static gint optmenu_use_ssl; /* Always SSL_NONE if !HAVE_SSL */
static gboolean selection_in_progress;
static gchar *
x_out(gchar *s)
{
gchar *p;
static gchar xbuf[32];
strncpy(xbuf, s, sizeof(xbuf));
xbuf[31] = '\0';
if ((p = strchr(xbuf, '_')) != NULL)
*p = ' ';
return xbuf;
}
static gchar *
default_port_of_proto(gint protocol, gint use_ssl)
{
gchar *port = NULL;
switch (protocol)
{
case PROTO_POP3:
if (use_ssl == SSL_TRANSPORT)
port = DEFAULT_POP3S_PORT;
else
port = DEFAULT_POP3_PORT;
break;
case PROTO_IMAP:
if (use_ssl == SSL_TRANSPORT)
port = DEFAULT_IMAPS_PORT;
else
port = DEFAULT_IMAP_PORT;
break;
}
return port;
}
static void
set_list_store_model_data(GtkListStore *store, GtkTreeIter *iter,
MailAccount *account)
{
gchar *protocol, *mailbox, *default_port = NULL, abuf[32], pbuf[32];
gchar *s;
#ifdef HAVE_SSL
gchar *use_ssl;
#endif
if (account->mboxtype == MBOX_REMOTE)
{
default_port = default_port_of_proto(account->protocol,
account->use_ssl);
snprintf(abuf, sizeof(abuf), "%s",
auth_string(account->protocol, account->authmech));
mailbox = g_strdup(account->server);
if (account->port != atoi(default_port))
{
snprintf(pbuf, sizeof(pbuf), ":%d", account->port);
s = g_strconcat(mailbox, pbuf, NULL);
g_free(mailbox);
mailbox = s;
}
s = g_strconcat(mailbox, " ", account->username, NULL);
g_free(mailbox);
mailbox = s;
if (account->protocol == PROTO_IMAP)
{
s = g_strconcat(mailbox, " ", account->imapfolder,
NULL);
g_free(mailbox);
mailbox = s;
}
}
else
{
snprintf(abuf, sizeof(abuf), "%s", mbox_strings[account->mboxtype & 0xf]);
mailbox = g_strdup_printf("%s", account->homedir_path ?
account->homedir_path : account->path);
}
protocol = x_out(abuf);
#ifdef HAVE_SSL
switch (account->use_ssl)
{
case SSL_TRANSPORT:
use_ssl = "SSL";
break;
case SSL_STARTTLS:
use_ssl = "STARTTLS";
break;
default:
use_ssl = "";
break;
}
#endif
gtk_list_store_set(store, iter,
PROTOCOL_COLUMN, protocol,
#ifdef HAVE_SSL
SSL_COLUMN, use_ssl,
#endif
MAILBOX_COLUMN, mailbox,
ACCOUNT_COLUMN, account,
-1);
g_free(mailbox);
}
static GtkTreeModel *
create_model(void)
{
GtkListStore *store;
GtkTreeIter iter;
GList *list;
MailAccount *account;
store = gtk_list_store_new(N_COLUMNS,
G_TYPE_STRING,
G_TYPE_STRING,
#ifdef HAVE_SSL
G_TYPE_STRING,
#endif
G_TYPE_POINTER);
for (list = config_mailbox_list; list; list = list->next)
{
account = (MailAccount *) list->data;
gtk_list_store_append(store, &iter);
set_list_store_model_data(store, &iter, account);
}
return GTK_TREE_MODEL(store);
}
static void
change_row_reference(GtkTreeModel *model, GtkTreePath *path)
{
gtk_tree_row_reference_free(row_reference);
if (model && path)
row_reference = gtk_tree_row_reference_new(model, path);
else
row_reference = NULL;
}
static void
reset_entries(void)
{
if (selection_in_progress)
return;
if (mbox_path_entry)
gtk_entry_set_text(GTK_ENTRY(mbox_path_entry), "");
gtk_entry_set_text(GTK_ENTRY(server_entry), "");
gtk_entry_set_text(GTK_ENTRY(user_entry), "");
gtk_entry_set_text(GTK_ENTRY(password_entry), "");
gtk_entry_set_text(GTK_ENTRY(imapfolder_entry), "");
gtk_widget_set_sensitive(imapfolder_entry, FALSE);
gtk_combo_box_set_active(GTK_COMBO_BOX(remote_combo_box), 0);
optmenu_auth_protocol = 0;
#ifdef HAVE_SSL
gtk_combo_box_set_active(GTK_COMBO_BOX(ssl_combo_box), 0);
#endif
optmenu_use_ssl = SSL_NONE;
gtk_entry_set_text(GTK_ENTRY(port_entry), DEFAULT_POP3_PORT);
gtk_widget_set_sensitive(port_entry, FALSE);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(port_button), FALSE);
change_row_reference(NULL, NULL);
gtk_tree_selection_unselect_all(selection);
}
static void
cb_mailbox_group(GtkWidget *widget, gpointer data)
{
gint group;
group = GPOINTER_TO_INT(data);
gtk_notebook_set_current_page(GTK_NOTEBOOK(account_notebook), group);
reset_entries();
}
static gboolean
default_port_entry(void)
{
gboolean active;
gchar* default_port = NULL;
active = GTK_TOGGLE_BUTTON(port_button)->active;
if (!active)
{
default_port = default_port_of_proto(
menu_to_proto(optmenu_auth_protocol),
optmenu_use_ssl);
gtk_entry_set_text(GTK_ENTRY(port_entry), default_port);
}
return active;
}
#ifdef HAVE_SSL
static void
cb_ssl_selected(GtkComboBox *widget)
{
optmenu_use_ssl = gtk_combo_box_get_active(widget);
default_port_entry();
}
#endif
static void
cb_specify_port(GtkWidget *widget, gpointer data)
{
gtk_widget_set_sensitive(port_entry, default_port_entry());
}
static void
cb_protocol_selected(GtkComboBox *widget)
{
optmenu_auth_protocol = gtk_combo_box_get_active(widget);
switch (menu_to_proto(optmenu_auth_protocol))
{
case PROTO_POP3:
gtk_entry_set_text(GTK_ENTRY(imapfolder_entry), "");
gtk_widget_set_sensitive(imapfolder_entry, FALSE);
break;
case PROTO_IMAP:
gtk_entry_set_text(GTK_ENTRY(imapfolder_entry),
"inbox");
gtk_widget_set_sensitive(imapfolder_entry, TRUE);
break;
}
default_port_entry();
}
static void
cb_tree_selection_changed(GtkTreeSelection *selection, gpointer data)
{
GtkTreeIter iter;
GtkTreeModel *model;
GtkTreePath *path;
MailAccount *account;
gchar buf[32];
gint default_port = 0;
gboolean active;
if (!gtk_tree_selection_get_selected(selection, &model, &iter))
{
reset_entries();
gtk_button_set_label(GTK_BUTTON(new_apply_button), GTK_STOCK_NEW);
gtk_widget_set_sensitive(delete_button, FALSE);
return;
}
path = gtk_tree_model_get_path(model, &iter);
change_row_reference(model, path);
gtk_tree_path_free(path);
gtk_button_set_label(GTK_BUTTON(new_apply_button), GTK_STOCK_APPLY);
gtk_widget_set_sensitive(delete_button, TRUE);
gtk_tree_model_get(model, &iter, ACCOUNT_COLUMN, &account, -1);
/* Below toggle of group button causes a callback -> reset_entries(),
| and I want to lock that out.
*/
selection_in_progress = TRUE;
if (account->mboxtype == MBOX_REMOTE)
{
if (remote_button)
gtk_toggle_button_set_active(
GTK_TOGGLE_BUTTON(remote_button), TRUE);
gtk_entry_set_text(GTK_ENTRY(server_entry), account->server);
gtk_entry_set_text(GTK_ENTRY(user_entry), account->username);
gtk_entry_set_text(GTK_ENTRY(password_entry), account->password);
optmenu_auth_protocol = menu_authtype(account->protocol,
account->authmech);
optmenu_use_ssl = account->use_ssl;
switch (account->protocol)
{
case PROTO_POP3:
gtk_entry_set_text(GTK_ENTRY(imapfolder_entry), "");
gtk_widget_set_sensitive(imapfolder_entry, FALSE);
break;
case PROTO_IMAP:
gtk_entry_set_text(GTK_ENTRY(imapfolder_entry),
account->imapfolder);
gtk_widget_set_sensitive(imapfolder_entry, TRUE);
break;
}
default_port = atoi(default_port_of_proto(account->protocol,
account->use_ssl));
gtk_combo_box_set_active(GTK_COMBO_BOX(remote_combo_box),
optmenu_auth_protocol);
#ifdef HAVE_SSL
gtk_combo_box_set_active(GTK_COMBO_BOX(ssl_combo_box),
optmenu_use_ssl);
#endif
if (account->port < 1)
account->port = default_port;
active = (account->port == default_port) ? FALSE : TRUE;
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(port_button), active);
snprintf(buf, sizeof(buf), "%d", account->port);
gtk_entry_set_text(GTK_ENTRY(port_entry), buf);
}
else if (local_supported)
{
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(local_button), TRUE);
gtk_entry_set_text(GTK_ENTRY(mbox_path_entry), account->homedir_path ?
account->homedir_path : account->path);
}
selection_in_progress = FALSE;
}
static gboolean
dup_entry(gchar **buf, GtkWidget **entry)
{
gchar *s;
s = gkrellm_gtk_entry_get_text(entry);
if (*s)
{
*buf = g_strdup(s);
return TRUE;
}
*buf = NULL;
return FALSE;
}
static void
sync_mail_list(void)
{
Mailbox *mbox;
MailAccount *account, *new_account;
GList *list;
/* Destroy MBOX_INTERNAL type mailboxes from the mailbox_list, then
| recreate them from the config list. Skip over the first FETCH mbox.
*/
for (list = mailbox_list->next; list; )
{
mbox = (Mailbox *) list->data;
if (mbox->account->mboxtype & MBOX_INTERNAL)
{
list = g_list_delete_link(list, list);
free_mailbox(mbox);
}
else
list = list->next;
}
checking_mh_mail = FALSE;
for (list = config_mailbox_list; list; list = list->next)
{
account = (MailAccount *) list->data;
new_account = g_new0(MailAccount, 1);
dup_account(new_account, account);
add_mailbox(new_account);
}
force_mail_check = TRUE;
}
static void
mailbox_enter_cb(void)
{
GtkTreeModel *model;
GtkTreePath *path;
GtkTreeIter iter;
GList *list;
MailAccount *new_account, *account;
gboolean remote, valid;
gint default_port = 0;
new_account = g_new0(MailAccount, 1);
valid = FALSE;
remote = remote_button ? GTK_TOGGLE_BUTTON(remote_button)->active : TRUE;
if (remote)
{
if ( dup_entry(&new_account->server, &server_entry)
&& dup_entry(&new_account->username, &user_entry)
&& dup_entry(&new_account->password, &password_entry)
)
valid = TRUE;
if (GTK_TOGGLE_BUTTON(port_button)->active)
new_account->port = atoi(gkrellm_gtk_entry_get_text(&port_entry));
new_account->mboxtype = MBOX_REMOTE;
new_account->protocol = menu_to_proto(optmenu_auth_protocol);
new_account->authmech = menu_to_mech(optmenu_auth_protocol);
new_account->use_ssl = optmenu_use_ssl;
if (new_account->protocol == PROTO_IMAP && valid)
valid = dup_entry(&new_account->imapfolder,
&imapfolder_entry);
default_port = atoi(default_port_of_proto(
new_account->protocol,
new_account->use_ssl));
if (new_account->port == 0)
new_account->port = default_port;
}
else if (mbox_path_entry)
{
valid = dup_entry(&new_account->path, &mbox_path_entry);
get_local_mboxtype(new_account);
}
if (!valid)
{
gkrellm_config_message_dialog(_("GKrellM Config Error"),
_("Incomplete mailbox entries"));
free_account(new_account);
return;
}
model = gtk_tree_view_get_model(treeview);
if (row_reference)
{
path = gtk_tree_row_reference_get_path(row_reference);
gtk_tree_model_get_iter(model, &iter, path);
gtk_tree_model_get(model, &iter, ACCOUNT_COLUMN, &account, -1);
list = g_list_find(config_mailbox_list, account);
free_account(account);
if (list)
list->data = (gpointer) new_account;
}
else
{
config_mailbox_list = g_list_append(config_mailbox_list, new_account);
gtk_list_store_append(GTK_LIST_STORE(model), &iter);
path = gtk_tree_model_get_path(model, &iter);
gtk_tree_view_scroll_to_cell(treeview, path, NULL, TRUE, 0.5, 0.5);
}
set_list_store_model_data(GTK_LIST_STORE(model), &iter, new_account);
reset_entries();
sync_mail_list();
}
static void
mailbox_delete_cb(GtkWidget *widget, gpointer *data)
{
GtkTreeModel *model;
GtkTreePath *path;
GtkTreeIter iter;
MailAccount *account;
if (!row_reference)
return;
model = gtk_tree_view_get_model(treeview);
path = gtk_tree_row_reference_get_path(row_reference);
gtk_tree_model_get_iter(model, &iter, path);
gtk_tree_model_get(model, &iter, ACCOUNT_COLUMN, &account, -1);
config_mailbox_list = g_list_remove(config_mailbox_list, account);
free_account(account);
gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
reset_entries();
sync_mail_list();
}
static void
cb_animation_mode(GtkWidget *button, gpointer data)
{
gint i = GPOINTER_TO_INT(data);
if (GTK_TOGGLE_BUTTON(button)->active)
animation_mode = i;
}
static void
cb_count_mode(GtkWidget *button, gpointer data)
{
gint i = GPOINTER_TO_INT(data);
if (GTK_TOGGLE_BUTTON(button)->active)
count_mode = i;
mail_text_decal->value = -1; /* Force a redraw */
force_mail_check = TRUE;
}
static void
toggle_button_cb(GtkToggleButton *button, gint *flag)
{
// *flag = gtk_toggle_button_get_active(button);
*flag = button->active;
}
static void
multi_toggle_button_cb(GtkWidget *button, gpointer data)
{
gint i;
cont_animation_mode = GTK_TOGGLE_BUTTON(enable_cont_anim_button)->active;
if (check_only_button)
fetch_check_only_mode = GTK_TOGGLE_BUTTON(check_only_button)->active;
reset_remote_mode = GTK_TOGGLE_BUTTON(reset_remote_button)->active;
i = GTK_TOGGLE_BUTTON(unseen_is_new_button)->active;
if (unseen_is_new != i)
force_mail_check = TRUE;
unseen_is_new = i;
super_mute_mode = GTK_TOGGLE_BUTTON(super_mute_button)->active;
mail_text_decal->value = -1; /* Force a redraw */
mua_inhibit_mode = GTK_TOGGLE_BUTTON(mua_inhibit_button)->active;
enable_multimua = GTK_TOGGLE_BUTTON(enable_multimua_button)->active;
show_tooltip = GTK_TOGGLE_BUTTON(show_tooltip_button)->active;
if (mh_seq_ignore_button)
mh_seq_ignore = GTK_TOGGLE_BUTTON(mh_seq_ignore_button)->active;
update_tooltip();
}
static void
cb_enable(GtkWidget *button, gpointer data)
{
enable_mail = GTK_TOGGLE_BUTTON(button)->active;
if (enable_mail)
{
gkrellm_panel_show(mail);
gkrellm_spacers_show(mon_mail);
}
else
{
gkrellm_panel_hide(mail);
gkrellm_spacers_hide(mon_mail);
}
}
static void
cb_mua(GtkWidget *widget, gpointer data)
{
gkrellm_dup_string(&mail_user_agent.command,
gkrellm_gtk_entry_get_text(&mail_user_agent_entry));
set_mua_button_sensitivity();
}
static void
remote_check_timeout_cb(GtkWidget *widget, GtkSpinButton *spin)
{
remote_check_timeout = gtk_spin_button_get_value_as_int(spin);
}
static void
local_check_timeout_cb(GtkWidget *widget, GtkSpinButton *spin)
{
local_check_timeout = gtk_spin_button_get_value_as_int(spin);
}
/* Go to some extra trouble here to avoid incomplete entries being used for
| fetch or notify commands which can trigger in the middle of entering the
| command. My UI instant apply behavior is that whatever is typed into the
| entry will be used and to be consistent with other entries I don't wan't
| to require an "enter" to "activate". So, save "changed" values and apply
| them when config is closed if user never hit the Enter key. (Is there
| a way for Gtk to auto activate?)
*/
static gchar *pending_fetch,
*pending_notify;
static void
cb_mail_fetch(GtkWidget *widget, gpointer data)
{
Mailproc *mp = (Mailproc *) mail_fetch->private;
gboolean activate_sig = GPOINTER_TO_INT(data);
gchar *s = gkrellm_gtk_entry_get_text(&mail_fetch_entry);
if (activate_sig)
{
if (gkrellm_dup_string(&mp->command, s))
reset_mail_fetch();
g_free(pending_fetch);
pending_fetch = NULL;
}
else /* "changed" sig, entry is pending on "activate" or config close */
gkrellm_dup_string(&pending_fetch, s);
}
static void
cb_mail_notify(GtkWidget *widget, gpointer data)
{
gboolean activate_sig = GPOINTER_TO_INT(data);
gchar *s = gkrellm_gtk_entry_get_text(&mail_notify_entry);
if (activate_sig)
{
gkrellm_dup_string(&mail_notify, s);
g_free(pending_notify);
pending_notify = NULL;
}
else /* "changed" sig, entry is pending on "activate" or config close */
gkrellm_dup_string(&pending_notify, s);
}
static void
config_destroyed(void)
{
GList *list;
MailAccount *account;
Mailproc *mp = (Mailproc *) mail_fetch->private;
if (pending_fetch && gkrellm_dup_string(&mp->command, pending_fetch))
reset_mail_fetch();
g_free(pending_fetch);
pending_fetch = NULL;
if (pending_notify)
gkrellm_dup_string(&mail_notify, pending_notify);
g_free(pending_notify);
pending_notify = NULL;
for (list = config_mailbox_list; list; list = list->next)
{
account = (MailAccount *) list->data;
free_account(account);
}
g_list_free(config_mailbox_list);
config_mailbox_list = NULL;
}
static void
copy_mailbox_accounts(void)
{
GList *list;
MailAccount *account;
Mailbox *mbox;
for (list = mailbox_list; list; list = list->next)
{
mbox = (Mailbox *) list->data;
if (! (mbox->account->mboxtype & MBOX_INTERNAL))
continue;
account = g_new0(MailAccount, 1);
dup_account(account, mbox->account);
config_mailbox_list = g_list_append(config_mailbox_list, account);
}
}
static gchar *mail_info_text0[] =
{
N_("<h>Mailboxes\n"),
N_("Mailboxes to monitor can be local or remote mailbox types.\n"),
N_("For local mailboxes, the path name may be a mbox file or it may be\n"
"a Maildir or MH mail style directory.\n"),
"\n"
};
static gchar *mail_info_text1[] =
{
N_("<h>Setup\n"),
N_("<b>Mail reading program\n"),
N_("If you enter a mail reading program (your mail user agent or MUA)\n"
"it can be launched by clicking on the mail monitor message count button.\n"),
"\n",
N_("<b>Sound notify program\n"),
N_("If you enter a notify (sound) command, it will be run when new mail\n"
"is detected.\n"),
"\n"
};
static gchar *mail_info_text2[] =
{
N_("<b>fetch/check Program\n"),
N_("If you want to download remote mail or check for remote mail without\n"
"using the builtin POP3 or IMAP mail checking which is set up in the\n"),
N_("<i>Mailboxes"),
N_(" tab, then do this by entering a mail fetch/check command.\n"
"For example, fetchmail can be run periodically to download mail\n"
"messages from a remote server to your local mailboxes where GKrellM\n"
"can count them.\n\n"
"Or, GKrellM can read the output and report the results from some mail\n"
"checking programs which count remote or local messages. If you enter\n"
"a command like this that checks mail without downloading, then you\n"
"must tell GKrellM this is the expected behaviour by selecting in the\n"
"Options tab:\n"),
N_("<i>\tFetch/check program checks messages only\n"),
N_("For checking messages on a remote server, GKrellM can read the\n"
"output of the program "),
N_("<i>fetchmail -c"),
N_(" (you must append the -c).\n"),
N_("But, do not combine these methods for the same mailbox! If you enter a\n"
"POP3 mailbox in the "),
N_("<i>Mailboxes"),
N_(" tab, then don't check it again with fetchmail.\n")
};
static gchar *mail_info_text3[] =
{
N_("<h>\nMouse Button Actions:\n"),
N_("<b>\tLeft "),
N_("click the mail count button to launch the mail reading program.\n"),
N_("\t\tIf options permit, also stop animations and reset remote counts.\n"),
N_("<b>\tLeft "),
N_("click the envelope decal to force a mail check regardless of\n"
"\t\tany mute mode or inhibit mail checking option settings.\n"),
N_("<b>\tMiddle "),
N_("click the mail panel to toggle a mute mode which inhibits\n"
"\t\tthe sound notify program and optionally inhibits all mail\n"
"\t\tchecking.\n"),
};
static void
create_mail_tab(GtkWidget *tab_vbox)
{
GtkWidget *tabs;
GtkWidget *table;
GtkWidget *vbox, *vbox1, *hbox, *hbox1;
GtkWidget *label;
GtkWidget *button;
GtkWidget *scrolled;
GtkWidget *text;
GtkTreeModel *model;
GtkCellRenderer *renderer;
GSList *group;
gint i;
row_reference = NULL;
mh_seq_ignore_button = NULL;
copy_mailbox_accounts();
tabs = gtk_notebook_new();
gtk_notebook_set_tab_pos(GTK_NOTEBOOK(tabs), GTK_POS_TOP);
gtk_box_pack_start(GTK_BOX(tab_vbox), tabs, TRUE, TRUE, 0);
g_signal_connect(G_OBJECT(tabs),"destroy",
G_CALLBACK(config_destroyed), NULL);
/* --Setup tab */
vbox = gkrellm_gtk_framed_notebook_page(tabs, _("Setup"));
gkrellm_gtk_check_button_connected(vbox, NULL,
enable_mail, FALSE, FALSE, 10,
cb_enable, NULL,
_("Enable Mailcheck"));
vbox1 = gkrellm_gtk_framed_vbox_end(vbox, NULL, 4, FALSE, 0, 2);
table = gtk_table_new(7, 2, FALSE /*non-homogeneous*/);
gtk_table_set_col_spacings(GTK_TABLE(table), 2);
gtk_table_set_row_spacings(GTK_TABLE(table), 3);
gtk_box_pack_start(GTK_BOX(vbox1), table, FALSE, FALSE, 2);
hbox = gtk_hbox_new(TRUE, 0);
/* Attach left right top bottom */
gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, 0, 1,
GTK_SHRINK, GTK_SHRINK, 0, 0);
label = gtk_label_new(_("Mail reading program:"));
gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 4);
mail_user_agent_entry = gtk_entry_new();
gtk_entry_set_max_length(GTK_ENTRY(mail_user_agent_entry), 255);
gtk_table_attach_defaults(GTK_TABLE(table), mail_user_agent_entry,
1, 2, 0, 1);
gtk_entry_set_text(GTK_ENTRY(mail_user_agent_entry),
mail_user_agent.command);
g_signal_connect(G_OBJECT(mail_user_agent_entry), "changed",
G_CALLBACK(cb_mua), NULL);
hbox = gtk_hbox_new(TRUE, 0);
gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, 1, 2,
GTK_SHRINK, GTK_SHRINK, 0, 0);
label = gtk_label_new(_("Notify (sound) program:"));
gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 4);
mail_notify_entry = gtk_entry_new();
gtk_entry_set_max_length(GTK_ENTRY(mail_notify_entry), 255);
gtk_table_attach_defaults(GTK_TABLE(table), mail_notify_entry, 1, 2, 1, 2);
gtk_entry_set_text(GTK_ENTRY(mail_notify_entry), mail_notify);
g_signal_connect(G_OBJECT(mail_notify_entry), "activate",
G_CALLBACK(cb_mail_notify), GINT_TO_POINTER(1));
g_signal_connect(G_OBJECT(mail_notify_entry), "changed",
G_CALLBACK(cb_mail_notify), GINT_TO_POINTER(0));
if (local_supported)
{
hbox = gtk_hbox_new(TRUE, 0);
gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, 2, 3,
GTK_SHRINK, GTK_SHRINK, 0, 0);
label = gtk_label_new(_("Mail fetch/check program:"));
gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 4);
mail_fetch_entry = gtk_entry_new();
gtk_entry_set_max_length(GTK_ENTRY(mail_fetch_entry), 255);
gtk_table_attach_defaults(GTK_TABLE(table), mail_fetch_entry,
1, 2, 2, 3);
gtk_entry_set_text(GTK_ENTRY(mail_fetch_entry),
((Mailproc *)mail_fetch->private)->command);
g_signal_connect(G_OBJECT(mail_fetch_entry), "activate",
G_CALLBACK(cb_mail_fetch), GINT_TO_POINTER(1));
g_signal_connect(G_OBJECT(mail_fetch_entry), "changed",
G_CALLBACK(cb_mail_fetch), GINT_TO_POINTER(0));
hbox = gtk_hbox_new(FALSE, 0);
gtk_table_attach_defaults(GTK_TABLE(table), hbox, 1, 2, 3, 4);
gkrellm_gtk_check_button_connected(hbox, NULL, fetch_check_is_local,
FALSE, FALSE, 3,
toggle_button_cb, &fetch_check_is_local,
_("Run fetch/check program at local interval"));
label = gtk_label_new(" ");
gtk_table_attach(GTK_TABLE(table), label, 0, 1, 4, 5,
GTK_SHRINK, GTK_SHRINK, 0, 0);
hbox = gtk_hbox_new(TRUE, 0);
gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, 5, 6,
GTK_SHRINK, GTK_SHRINK, 0, 0);
label = gtk_label_new(_("Check local mailboxes every"));
gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 4);
hbox = gtk_hbox_new(FALSE, 0);
gtk_table_attach_defaults(GTK_TABLE(table), hbox, 1, 2, 5, 6);
gkrellm_gtk_spin_button(hbox, NULL,
(gfloat) local_check_timeout, 2.0, 100.0, 1.0, 5.0, 0, 60,
local_check_timeout_cb, NULL, FALSE, _("seconds"));
}
hbox = gtk_hbox_new(FALSE, 0);
gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, 6, 7,
GTK_SHRINK, GTK_SHRINK, 0, 0);
if (local_supported)
label = gtk_label_new(_("Do remote checks every"));
else
label = gtk_label_new(_("Check mail every"));
gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 4);
hbox = gtk_hbox_new(FALSE, 0);
gtk_table_attach_defaults(GTK_TABLE(table), hbox, 1, 2, 6, 7);
gkrellm_gtk_spin_button(hbox, NULL,
(gfloat) remote_check_timeout, 1.0, 60.0, 1.0, 5.0, 0, 60,
remote_check_timeout_cb, NULL, FALSE, _("minutes"));
/* --Mailboxes tab */
vbox = gkrellm_gtk_framed_notebook_page(tabs, _("Mailboxes"));
// vbox1 = gkrellm_gtk_framed_vbox(vbox, NULL, 3, FALSE, 0, 3);
vbox1 = gtk_vbox_new(FALSE, 0);
gtk_container_add(GTK_CONTAINER(vbox), vbox1);
account_notebook = gtk_notebook_new();
gtk_box_pack_start(GTK_BOX(vbox1), account_notebook, FALSE, FALSE, 2);
gtk_notebook_set_show_tabs(GTK_NOTEBOOK(account_notebook), FALSE);
/* Local mailbox account entry */
if (local_supported)
{
vbox1 = gtk_vbox_new(FALSE, 0);
gtk_notebook_append_page(GTK_NOTEBOOK(account_notebook), vbox1, NULL);
hbox = gtk_hbox_new(FALSE, 2);
gtk_box_pack_start(GTK_BOX(vbox1), hbox, FALSE, FALSE, 0);
label = gtk_label_new(_("Path name:"));
gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
mbox_path_entry = gtk_entry_new();
gtk_entry_set_max_length(GTK_ENTRY(mbox_path_entry), 255);
gtk_box_pack_start(GTK_BOX(hbox), mbox_path_entry, TRUE, TRUE, 2);
gtk_entry_set_text(GTK_ENTRY(mbox_path_entry), "");
g_signal_connect (G_OBJECT (mbox_path_entry), "activate",
G_CALLBACK(mailbox_enter_cb), NULL);
}
/* Remote mailbox account entry */
table = gtk_table_new(4 /* rows */, 4, FALSE /*non-homogeneous*/);
gtk_table_set_row_spacings(GTK_TABLE(table), 2);
gtk_table_set_col_spacings(GTK_TABLE(table), 6);
gtk_table_set_col_spacing(GTK_TABLE(table), 1, 16);
gtk_notebook_append_page(GTK_NOTEBOOK(account_notebook), table, NULL);
label = gtk_label_new(_("Server"));
gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1);
server_entry = gtk_entry_new();
gtk_entry_set_max_length(GTK_ENTRY(server_entry), 255);
gtk_table_attach_defaults(GTK_TABLE(table), server_entry, 1, 2, 0, 1);
label = gtk_label_new(_("User name"));
gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
user_entry = gtk_entry_new();
gtk_entry_set_max_length(GTK_ENTRY(user_entry), 255);
gtk_table_attach_defaults(GTK_TABLE(table), user_entry, 1, 2, 1, 2);
label = gtk_label_new(_("Password"));
gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3);
password_entry = gtk_entry_new();
gtk_entry_set_max_length(GTK_ENTRY(password_entry), 255);
gtk_table_attach_defaults(GTK_TABLE(table), password_entry, 1, 2, 2, 3);
gtk_entry_set_visibility(GTK_ENTRY(password_entry), FALSE);
label = gtk_label_new(_("Protocol"));
gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
gtk_table_attach_defaults(GTK_TABLE(table), label, 2, 3, 0, 1);
remote_combo_box = gtk_combo_box_new_text();
gtk_table_attach_defaults(GTK_TABLE(table), remote_combo_box, 3, 4, 0,1);
for (i = 0; auth_strings[i].string != NULL; ++i)
gtk_combo_box_append_text(GTK_COMBO_BOX(remote_combo_box), auth_strings[i].string);
g_signal_connect(G_OBJECT(remote_combo_box), "changed",
G_CALLBACK(cb_protocol_selected), NULL);
i = 1;
#ifdef HAVE_SSL
label = gtk_label_new(_("Use SSL"));
gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
gtk_table_attach_defaults(GTK_TABLE(table), label, 2, 3, i, i+1);
ssl_combo_box = gtk_combo_box_new_text();
gtk_table_attach_defaults(GTK_TABLE(table), ssl_combo_box, 3, 4, i, i+1);
++i;
gtk_combo_box_append_text(GTK_COMBO_BOX(ssl_combo_box), _("No"));
gtk_combo_box_append_text(GTK_COMBO_BOX(ssl_combo_box), "SSL");
gtk_combo_box_append_text(GTK_COMBO_BOX(ssl_combo_box), "STARTTLS");
g_signal_connect(G_OBJECT(ssl_combo_box), "changed",
G_CALLBACK(cb_ssl_selected), NULL);
#endif
label = gtk_label_new(_("IMAP folder"));
gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5);
gtk_table_attach_defaults(GTK_TABLE(table), label, 2, 3, i, i+1);
imapfolder_entry = gtk_entry_new();
gtk_entry_set_max_length(GTK_ENTRY(imapfolder_entry), 255);
gtk_table_attach_defaults(GTK_TABLE(table), imapfolder_entry,
3, 4, i, i+1);
++i;
gkrellm_gtk_check_button_connected(NULL, &port_button,
FALSE, FALSE, FALSE, 0, cb_specify_port, NULL,
_("Specify port"));
hbox = gtk_hbox_new(TRUE, 0);
gtk_table_attach_defaults(GTK_TABLE(table), hbox, 2, 3, i, i+1);
gtk_box_pack_start(GTK_BOX(hbox), port_button, FALSE, FALSE, 0);
port_entry = gtk_entry_new();
gtk_entry_set_max_length(GTK_ENTRY(port_entry), 255);
gtk_table_attach_defaults(GTK_TABLE(table), port_entry, 3, 4, i, i+1);
gtk_widget_set_sensitive(port_entry, FALSE);
hbox1 = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), hbox1, FALSE, FALSE, 0);
hbox = gtk_hbutton_box_new();
gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_END);
gtk_box_set_spacing(GTK_BOX(hbox), 5);
gtk_box_pack_end(GTK_BOX(hbox1), hbox, FALSE, FALSE, 0);
if (local_supported)
{
local_button = gtk_radio_button_new_with_label(NULL,
_("Local mailbox"));
gtk_box_pack_start(GTK_BOX(hbox1), local_button, FALSE, FALSE, 5);
g_signal_connect(G_OBJECT(local_button), "clicked",
G_CALLBACK(cb_mailbox_group), GINT_TO_POINTER(0));
remote_button = gtk_radio_button_new_with_label_from_widget(
GTK_RADIO_BUTTON(local_button), _("Remote mailbox"));
gtk_box_pack_start(GTK_BOX(hbox1), remote_button, FALSE, FALSE, 0);
g_signal_connect(G_OBJECT(remote_button), "clicked",
G_CALLBACK(cb_mailbox_group), GINT_TO_POINTER(1));
}
else
default_port_entry();
gkrellm_gtk_button_connected(hbox, &delete_button, FALSE, FALSE, 5,
mailbox_delete_cb, NULL, GTK_STOCK_DELETE);
gtk_widget_set_sensitive(delete_button, FALSE);
gkrellm_gtk_button_connected(hbox, &new_apply_button, FALSE, FALSE, 5,
mailbox_enter_cb, NULL, GTK_STOCK_NEW);
vbox1 = gkrellm_gtk_framed_vbox(vbox, NULL, 6, TRUE, 0, 3);
scrolled = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_box_pack_start(GTK_BOX(vbox1), scrolled, TRUE, TRUE, 0);
model = create_model();
treeview = GTK_TREE_VIEW(gtk_tree_view_new_with_model(model));
g_object_unref(G_OBJECT(model));
gtk_tree_view_set_rules_hint(treeview, TRUE);
renderer = gtk_cell_renderer_text_new();
gtk_tree_view_insert_column_with_attributes(treeview, -1, _("Protocol"),
renderer,
"text", PROTOCOL_COLUMN, NULL);
#ifdef HAVE_SSL
renderer = gtk_cell_renderer_text_new();
gtk_tree_view_insert_column_with_attributes(treeview, -1, "SSL",
renderer,
"text", SSL_COLUMN, NULL);
#endif
renderer = gtk_cell_renderer_text_new();
gtk_tree_view_insert_column_with_attributes(treeview, -1, _("Mailbox"),
renderer,
"text", MAILBOX_COLUMN, NULL);
gtk_container_add(GTK_CONTAINER(scrolled), GTK_WIDGET(treeview));
selection = gtk_tree_view_get_selection(treeview);
gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
g_signal_connect(G_OBJECT(selection), "changed",
G_CALLBACK(cb_tree_selection_changed), NULL);
/* --Animation tab */
vbox = gkrellm_gtk_framed_notebook_page(tabs, _("Animation"));
vbox1 = gkrellm_gtk_category_vbox(vbox,
_("Animation Select"),
4, 0, TRUE);
hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox1), hbox, FALSE, FALSE, 0);
anim_button[0] = gtk_radio_button_new_with_label(NULL, _("None"));
gtk_box_pack_start(GTK_BOX(hbox), anim_button[0], TRUE, TRUE, 0);
anim_button[1] = gtk_radio_button_new_with_label_from_widget(
GTK_RADIO_BUTTON(anim_button[0]), _("Envelope"));
gtk_box_pack_start(GTK_BOX(hbox), anim_button[1], TRUE, TRUE, 0);
anim_button[2] = gtk_radio_button_new_with_label_from_widget(
GTK_RADIO_BUTTON(anim_button[1]),
#ifdef BSD
_("Daemon"));
#else
_("Penguin"));
#endif
gtk_box_pack_start(GTK_BOX(hbox), anim_button[2], TRUE, TRUE, 0);
anim_button[3] = gtk_radio_button_new_with_label_from_widget(
GTK_RADIO_BUTTON(anim_button[2]), _("Both"));
gtk_box_pack_start(GTK_BOX(hbox), anim_button[3], TRUE, TRUE, 0);
button = anim_button[animation_mode];
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
for (i = 0; i < 4; ++i)
g_signal_connect(G_OBJECT(anim_button[i]), "toggled",
G_CALLBACK(cb_animation_mode), GINT_TO_POINTER(i));
gkrellm_gtk_check_button_connected(vbox, &enable_cont_anim_button,
cont_animation_mode, FALSE, FALSE, 10,
multi_toggle_button_cb, NULL,
_("Run animation continuously as long as there is a new mail count"));
/* --Options tab */
vbox = gkrellm_gtk_framed_notebook_page(tabs, _("Options"));
vbox1 = gkrellm_gtk_category_vbox(vbox,
_("Message Counting"),
4, 0, TRUE);
hbox = gtk_hbox_new (FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox1), hbox, FALSE, FALSE, 0);
button = gtk_radio_button_new_with_label(NULL, _("new/total"));
gtk_box_pack_start(GTK_BOX (hbox), button, TRUE, TRUE, 0);
group = gtk_radio_button_get_group(GTK_RADIO_BUTTON (button));
count_mode_button[0] = button;
button = gtk_radio_button_new_with_label(group, _("new"));
gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
group = gtk_radio_button_get_group(GTK_RADIO_BUTTON (button));
count_mode_button[1] = button;
button = gtk_radio_button_new_with_label(group, _("do not count"));
gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
count_mode_button[2] = button;
button = count_mode_button[count_mode];
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
for (i = 0; i < 3; ++i)
g_signal_connect(G_OBJECT(count_mode_button[i]), "toggled",
G_CALLBACK(cb_count_mode), GINT_TO_POINTER(i));
if (local_supported)
gkrellm_gtk_check_button_connected(vbox, &check_only_button,
fetch_check_only_mode, TRUE, TRUE, 0,
multi_toggle_button_cb, NULL,
_("Fetch/check program checks messages only (see Mail Info tab)"));
gkrellm_gtk_check_button_connected(vbox, &reset_remote_button,
reset_remote_mode, TRUE, TRUE, 0,
multi_toggle_button_cb, NULL,
_("Reset remote message counts when message count button is pressed."));
gkrellm_gtk_check_button_connected(vbox, &unseen_is_new_button,
unseen_is_new, TRUE, TRUE, 0,
multi_toggle_button_cb, NULL,
_("Count accessed but unseen mail as new (if this status is available)"));
gkrellm_gtk_check_button_connected(vbox, &super_mute_button,
super_mute_mode, TRUE, TRUE, 0,
multi_toggle_button_cb, NULL,
_("Mute mode inhibits all mail checking, not just notify (sound) program"));
gkrellm_gtk_check_button_connected(vbox, &mua_inhibit_button,
mua_inhibit_mode, TRUE, TRUE, 0,
multi_toggle_button_cb, NULL,
_("Inhibit all mail checking while the mail reader is running"));
gkrellm_gtk_check_button_connected(vbox, &enable_multimua_button,
enable_multimua, TRUE, TRUE, 0,
multi_toggle_button_cb, NULL,
_("Allow multiple launches of the mail reader program"));
gkrellm_gtk_check_button_connected(vbox, &show_tooltip_button,
show_tooltip, TRUE, TRUE, 0,
multi_toggle_button_cb, NULL,
_("List mailboxes containing new mail in a tooltip"));
if (checking_mh_mail && have_mh_sequences)
gkrellm_gtk_check_button_connected(vbox, &mh_seq_ignore_button,
mh_seq_ignore, TRUE, TRUE, 0,
multi_toggle_button_cb, NULL,
_("Ignore .mh_sequences when checking MH mail."));
/* --Info tab */
vbox = gkrellm_gtk_framed_notebook_page(tabs, _("Info"));
text = gkrellm_gtk_scrolled_text_view(vbox, NULL,
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
if (local_supported)
for (i = 0; i < sizeof(mail_info_text0)/sizeof(gchar *); ++i)
gkrellm_gtk_text_view_append(text, _(mail_info_text0[i]));
for (i = 0; i < sizeof(mail_info_text1)/sizeof(gchar *); ++i)
gkrellm_gtk_text_view_append(text, _(mail_info_text1[i]));
if (local_supported)
for (i = 0; i < sizeof(mail_info_text2)/sizeof(gchar *); ++i)
gkrellm_gtk_text_view_append(text, _(mail_info_text2[i]));
for (i = 0; i < sizeof(mail_info_text3)/sizeof(gchar *); ++i)
gkrellm_gtk_text_view_append(text, _(mail_info_text3[i]));
}
static GkrellmMonitor monitor_mail =
{
N_("Mail"), /* Name, for config tab. */
MON_MAIL, /* Id, 0 if a plugin */
create_mail, /* The create function */
update_mail, /* The update function */
create_mail_tab, /* The config tab create function */
NULL, /* Instant apply */
save_mail_config, /* Save user conifg */
load_mail_config, /* Load user config */
"mail", /* config keyword */
NULL, /* Undef 2 */
NULL, /* Undef 1 */
NULL, /* Undef 0 */
0, /* insert_before_id - place plugin before this mon */
NULL, /* Handle if a plugin, filled in by GKrellM */
NULL /* path if a plugin, filled in by GKrellM */
};
GkrellmMonitor *
gkrellm_init_mail_monitor(void)
{
#ifdef HAVE_SSL
#ifndef HAVE_GNUTLS
int i, num_locks = CRYPTO_num_locks();
#endif
#endif
monitor_mail.name = _(monitor_mail.name);
enable_mail = TRUE;
show_tooltip = TRUE;
#if defined(WIN32)
// Defaulting to TRUE makes sense on windows as most MUAs will raise their
// window if they're started a second time (i.e. clicking the button in
// gkrellm will raise an existing mail-window)
enable_multimua = TRUE;
#else
enable_multimua = FALSE;
#endif
cont_animation_mode = FALSE;
super_mute_mode = FALSE;
mua_inhibit_mode = FALSE;
_GK.decal_mail_frames = 18;
_GK.decal_mail_delay = 1;
#ifdef HAVE_GNUTLS
#if GNUTLS_VERSION_NUMBER <= 0x020b00
/* gcrypt mutex setup, only needed for GnuTLS < 2.12 */
gcry_control (GCRYCTL_SET_THREAD_CBS, &gk_gcry_threads_glib);
#endif
gnutls_global_init();
SSL_load_error_strings();
SSL_library_init();
#else
#ifdef HAVE_SSL
SSL_load_error_strings();
SSL_library_init();
ssl_locks = g_new(GMutex *, num_locks);
for (i = 0; i < num_locks; i++)
{
ssl_locks[i] = g_new(GMutex, 1);
g_mutex_init(ssl_locks[i]);
}
CRYPTO_set_locking_callback(ssl_locking_cb);
#endif
#endif
mail_fetch = g_new0(Mailbox, 1);
mail_fetch->account = g_new0(MailAccount, 1);
mail_fetch->private = g_new0(Mailproc, 1);
mail_fetch->account->mboxtype = MBOX_FETCH;
mail_fetch->check_func = run_fetch_program;
mailbox_list = g_list_append(mailbox_list, mail_fetch);
((Mailproc *)(mail_fetch->private))->command = g_strdup("");
((Mailproc *)(mail_fetch->private))->pipe = -1;
mail_user_agent.command = g_strdup("");
mail_user_agent.pipe = -1;
mail_notify = g_strdup("");
style_id = gkrellm_add_meter_style(&monitor_mail, MAIL_STYLE_NAME);
force_mail_check = TRUE; /* Do a check at startup */
mon_mail = &monitor_mail;
return &monitor_mail;
}
/* ====================== Exported Mail Functions =====================*/
gboolean
gkrellm_get_mail_mute_mode(void)
{
return mute_mode;
}
/* ====================================================================*/
/* Functions to allow for external mailboxes, as in plugins which want
| to check for alternate mailbox types but have the mail counts reported
| by the builtin mailbox monitor. A plugin outline would be:
|
| typedef struct
| {
| gpointer mbox_ptr;
| ... local plugin stuff ...
| } PluginMbox;
|
| PluginMbox pmbox;
|
| void create_plugin_mbox(GtkWidget *vbox, gint first_create)
| { //See plugin programmers reference for general structure
| ...
| pmbox.mbox_ptr = gkrellm_add_external_mbox(pmbox_check, TRUE, &pmbox);
| gkrellm_set_external_mbox_tooltip(pmbox.mbox_ptr, "mbox_name_stuff");
| ...
| }
|
| gint pmbox_check(PluginMbox *pmb)
| { //Collect info from the mail box
| gkrellm_set_external_mbox_counts(pmb->mbox_ptr, total_mail, new_mail);
| }
*/
/* External mailbox counts won't show in the tooltip unless you call this.
| If a plugin wants only to use the sound/animation feature and not show
| up in a tooltip, then do not make this call.
*/
void
gkrellm_set_external_mbox_tooltip(gpointer mbox_ptr, gchar *string)
{
Mailbox *mbox = (Mailbox *) mbox_ptr;
gkrellm_dup_string(&mbox->account->path, string);
}
/* Set total and new message counts for an external mailbox so the counts
| can appear in a tooltip (must also call above routine), and so the
| animation/sound can be triggered. Since sound and animation is triggered
| synchronously with remote and local checks, a plugin should
| call this routine from within a check_func that is setup in the
| gkrellm_add_external_mbox() routine.
*/
void
gkrellm_set_external_mbox_counts(gpointer mbox_ptr, gint total, gint new)
{
Mailbox *mbox = (Mailbox *) mbox_ptr;
mbox->mail_count = total;
mbox->new_mail_count = new;
}
/* A plugin can have a check_func() called at the mail monitors local
| check interval (threaded is FALSE) or at the remote check interval
| (threaded is TRUE). Additionally, if threaded is TRUE, the check_func
| will be called as a thread. The data pointer is a pointer to a plugin
| defined structure which specifies a plugins unique mailbox. This data
| pointer will be passed as the argument to check_func(data) when it
| is called at the update intervals. gkrellm_add_external_mbox() returns
| the internal Mailbox * which should be treated simply as a gpointer
| in the plugin and it must be used as the first argument to the above
| gkrellm_set_external_tooltip() and gkrellm_set_external_counts().
*/
gpointer
gkrellm_add_external_mbox(gboolean (*check_func)(), gboolean threaded,
gpointer data)
{
Mailbox *mbox;
mbox = g_new0(Mailbox, 1);
mbox->account = g_new0(MailAccount, 1);
mbox->account->mboxtype = threaded ? MBOX_REMOTE_PLUGIN :MBOX_LOCAL_PLUGIN;
mbox->check_func = check_func;
mbox->data = data;
mailbox_list = g_list_append(mailbox_list, mbox);
return (gpointer) mbox;
}
void
gkrellm_destroy_external_mbox(gpointer mbox_ptr)
{
GList *tmp;
if ((tmp = g_list_find(mailbox_list, mbox_ptr)) == NULL)
return;
mailbox_list = g_list_remove_link(mailbox_list, tmp);
g_list_free(tmp);
free_mailbox(mbox_ptr);
}
/* =======================================================================*/