554 lines
13 KiB
C
554 lines
13 KiB
C
|
/****************************************************************************/
|
||
|
/*
|
||
|
* imapsocket.c -- Module to check for mail using an IMAP socket
|
||
|
*
|
||
|
* Functions to logon to an IMAP server and check the user's INBOX for
|
||
|
* RECENT or UNSEEN mail. Errors may be logged to ~/.xsession-errors if
|
||
|
* stderr is redirected by a call to RedirectErrLog(), otherwise they are
|
||
|
* written to stderr.
|
||
|
*
|
||
|
* It is intended to be used as a set of library functions by a program
|
||
|
* that displays and icon, lights a keyboard LED or otherwise notifies
|
||
|
* a user that mail is waiting to be read.
|
||
|
*
|
||
|
* Author: Michael P. Duane mduane@seanet.com
|
||
|
* Date: August 12, 1997
|
||
|
*
|
||
|
* Copyright (c) 1997-98 by Michael P. Duane
|
||
|
*
|
||
|
* Permission to use, copy, modify, and distribute this software and its
|
||
|
* documentation for any purpose and without fee is hereby granted,
|
||
|
* provided that the above copyright notice appear in all copies and that
|
||
|
* both that copyright notice and this permission notice appear in
|
||
|
* supporting documentation.
|
||
|
*
|
||
|
* This file is provided AS IS with no warranties of any kind. The author
|
||
|
* shall have no liability with respect to the infringement of copyrights,
|
||
|
* trade secrets or any patents by this file or any part thereof. In no
|
||
|
* event will the author be liable for any lost revenue or profits or
|
||
|
* other special, indirect and consequential damages.
|
||
|
*
|
||
|
* Revision History:
|
||
|
*
|
||
|
****************************************************************************/
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <errno.h>
|
||
|
#include <ctype.h>
|
||
|
#include <netinet/in.h>
|
||
|
#include <arpa/inet.h>
|
||
|
#include <netdb.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <string.h>
|
||
|
#include <stdarg.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#ifndef FALSE
|
||
|
#define FALSE 0
|
||
|
#define TRUE ~FALSE
|
||
|
#endif
|
||
|
|
||
|
#define MAX_BUFFER_SIZE 8192
|
||
|
#define MAX_BUFFER_LINES 32
|
||
|
|
||
|
#define SOCKET_ERROR (-1)
|
||
|
|
||
|
char *my_name = "";
|
||
|
|
||
|
int fdImap = (-1);
|
||
|
struct sockaddr_in addrImap;
|
||
|
|
||
|
int seq_num = 0;
|
||
|
char tag [ 8];
|
||
|
|
||
|
char hostname [64] = "";
|
||
|
short port = 0;
|
||
|
|
||
|
char user [64] = "";
|
||
|
char passwd [64] = "";
|
||
|
|
||
|
char recv_buf [MAX_BUFFER_SIZE];
|
||
|
char *line [MAX_BUFFER_LINES];
|
||
|
|
||
|
/****************************************************************************/
|
||
|
/*
|
||
|
* GetProgramName()
|
||
|
*
|
||
|
* Extract just the basename from argv[0]. 'basename()' doesn't exist
|
||
|
* on all systems.
|
||
|
*/
|
||
|
char *GetProgramName( char *fullname )
|
||
|
{
|
||
|
char *name;
|
||
|
|
||
|
return( name = ( (name = strrchr( fullname, '/' )) ? ++name : fullname ) );
|
||
|
|
||
|
} /* GetProgramName */
|
||
|
|
||
|
/****************************************************************************/
|
||
|
/*
|
||
|
* RedirectErrLog()
|
||
|
*
|
||
|
* Redirect stderr to $HOME/.xsesson-errors. Create it if it doesn't
|
||
|
* exist, append to it if it does.
|
||
|
*
|
||
|
*/
|
||
|
int RedirectErrLog( void )
|
||
|
{
|
||
|
char *home;
|
||
|
char xsesserr [255];
|
||
|
int mode = (O_CREAT | O_APPEND | O_WRONLY);
|
||
|
int fderr;
|
||
|
|
||
|
if ( (home = getenv( "HOME" )) != NULL ) {
|
||
|
strcat( strcpy( xsesserr, home ), "/.xsession-errors" );
|
||
|
if ( (fderr = open( xsesserr, mode, 0600 )) > 0 ) {
|
||
|
close( STDERR_FILENO );
|
||
|
if ( dup( fderr ) == STDERR_FILENO )
|
||
|
close( fderr );
|
||
|
}
|
||
|
return( 0 );
|
||
|
}
|
||
|
|
||
|
return( -1 );
|
||
|
|
||
|
}/* RedirectErrLog */
|
||
|
|
||
|
/****************************************************************************/
|
||
|
/*
|
||
|
* LogMessage()
|
||
|
*
|
||
|
* Prepend all error messages with my program name and log the corresponding
|
||
|
* errno description string where appropriate.
|
||
|
*/
|
||
|
void LogMessage( char *msg, int errval )
|
||
|
{
|
||
|
|
||
|
if ( errval )
|
||
|
fprintf( stderr, "%s: %s, %s\n", my_name, msg, strerror( errval ) );
|
||
|
else
|
||
|
fprintf( stderr, "%s: %s\n", my_name, msg );
|
||
|
|
||
|
} /* LogMessage */
|
||
|
|
||
|
/****************************************************************************/
|
||
|
/*
|
||
|
* ParseToken()
|
||
|
*
|
||
|
* Validate the "token = value" sequence to include a known token and
|
||
|
* a valid assignment operator. Store the value in a global on success.
|
||
|
*/
|
||
|
static void ParseToken( char *token, char *assign, char *value )
|
||
|
{
|
||
|
char errmsg [255];
|
||
|
int i;
|
||
|
|
||
|
for ( i=0; i< strlen( token ); i++ )
|
||
|
*(token+i) = toupper( *(token+i) );
|
||
|
|
||
|
if ( strcmp( assign, "=" ) ) {
|
||
|
sprintf( errmsg, "\"%s\" missing assignment", token );
|
||
|
LogMessage( errmsg, 0 );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( !strcmp( token, "HOSTNAME" ) )
|
||
|
strcpy( hostname, value );
|
||
|
else if ( !strcmp( token, "PORT" ) )
|
||
|
port = (short)strtol( value, (char **)NULL, 0 );
|
||
|
else if ( !strcmp( token, "USER" ) )
|
||
|
strcpy( user, value );
|
||
|
else if ( !strcmp( token, "PASSWORD" ) )
|
||
|
strcpy( passwd, value );
|
||
|
else {
|
||
|
sprintf( errmsg, "Unexpected configuration token: \"%s\"", token );
|
||
|
LogMessage( errmsg, 0 );
|
||
|
}
|
||
|
|
||
|
} /* ParseToken */
|
||
|
|
||
|
/****************************************************************************/
|
||
|
static char *GetNextToken( char *str )
|
||
|
{
|
||
|
|
||
|
return( strtok( str, " \t\n\r" ) );
|
||
|
|
||
|
} /* GetNextToken */
|
||
|
|
||
|
/****************************************************************************/
|
||
|
/*
|
||
|
* GetImapCfgInfo()
|
||
|
*
|
||
|
* Reads the program configuration file looking for assignments of the
|
||
|
* form "token = value". '#' begins a comment that contiues to EOL.
|
||
|
*/
|
||
|
int GetImapCfgInfo( char *cfgfile )
|
||
|
{
|
||
|
FILE *cfg;
|
||
|
char txtbuf [512];
|
||
|
char *txt;
|
||
|
char *tok;
|
||
|
char *assign;
|
||
|
char *val;
|
||
|
|
||
|
if ( (cfg = fopen( cfgfile, "r" )) != NULL ) {
|
||
|
do {
|
||
|
if ( (txt = fgets( txtbuf, sizeof( txtbuf ), cfg )) != NULL) {
|
||
|
if ( (tok = GetNextToken( txt )) ) {
|
||
|
assign = val = NULL;
|
||
|
if ( strlen( tok ) ) {
|
||
|
if ( strchr( tok, '#' ) )
|
||
|
continue;
|
||
|
assign = GetNextToken( NULL );
|
||
|
val = GetNextToken( NULL );
|
||
|
GetNextToken( NULL ); /* strip to eol */
|
||
|
}
|
||
|
if ( assign && val )
|
||
|
ParseToken( tok, assign, val );
|
||
|
}
|
||
|
}
|
||
|
} while( !feof( cfg ) );
|
||
|
fclose( cfg );
|
||
|
}
|
||
|
else {
|
||
|
LogMessage( cfgfile, errno );
|
||
|
return( -1 );
|
||
|
}
|
||
|
|
||
|
return( 0 );
|
||
|
|
||
|
} /* GetImapCfgInfo */
|
||
|
|
||
|
/****************************************************************************/
|
||
|
/*
|
||
|
* InitSocketAddr()
|
||
|
*
|
||
|
* Setup and validate the host/port address for the IMAP socket
|
||
|
*/
|
||
|
|
||
|
int InitSocketAddr( void )
|
||
|
{
|
||
|
struct hostent *host_info;
|
||
|
char addr_str [ 32];
|
||
|
|
||
|
if ( (host_info = gethostbyname( hostname )) == NULL ) {
|
||
|
LogMessage( "Host name error", errno );
|
||
|
return( -1 );
|
||
|
}
|
||
|
|
||
|
sprintf( addr_str,"%u.%u.%u.%u",
|
||
|
(unsigned char)host_info->h_addr_list[0][0],
|
||
|
(unsigned char)host_info->h_addr_list[0][1],
|
||
|
(unsigned char)host_info->h_addr_list[0][2],
|
||
|
(unsigned char)host_info->h_addr_list[0][3]
|
||
|
);
|
||
|
|
||
|
addrImap.sin_family = PF_INET;
|
||
|
addrImap.sin_addr.s_addr = inet_addr( addr_str );
|
||
|
addrImap.sin_port = htons( port );
|
||
|
|
||
|
if ( addrImap.sin_addr.s_addr == INADDR_NONE ) {
|
||
|
LogMessage( "Socket Address Error", errno );
|
||
|
return( -1 );
|
||
|
}
|
||
|
|
||
|
return( 0 );
|
||
|
|
||
|
} /* InitSocketAddr */
|
||
|
|
||
|
/****************************************************************************/
|
||
|
/*
|
||
|
* ConnectSocket()
|
||
|
*
|
||
|
* Open and connect to the IMAP socket
|
||
|
*/
|
||
|
|
||
|
static int ConnectSocket( struct sockaddr_in *addrImap )
|
||
|
{
|
||
|
|
||
|
if ( addrImap->sin_addr.s_addr == INADDR_NONE ) {
|
||
|
LogMessage( "Socket Address Error", errno );
|
||
|
return( -1 );
|
||
|
}
|
||
|
|
||
|
if ( (fdImap = socket( AF_INET, SOCK_STREAM, 0 )) == SOCKET_ERROR ) {
|
||
|
LogMessage( "Error opening socket", errno );
|
||
|
return( -1 );
|
||
|
}
|
||
|
|
||
|
if ( connect( fdImap, (struct sockaddr *)addrImap,
|
||
|
sizeof( struct sockaddr )) == SOCKET_ERROR ) {
|
||
|
close( fdImap );
|
||
|
fdImap = (-1);
|
||
|
LogMessage( "Socket Connection error", errno );
|
||
|
return( -1 );
|
||
|
}
|
||
|
|
||
|
return( 0 );
|
||
|
|
||
|
} /* ConnectSocket */
|
||
|
|
||
|
/****************************************************************************/
|
||
|
/*
|
||
|
* OpenImapSocket()
|
||
|
*
|
||
|
* Connect to the IMAP socket and make sure the IMAP service responds.
|
||
|
*/
|
||
|
|
||
|
static int OpenImapSocket( struct sockaddr_in *addrImap )
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
if ( ConnectSocket( addrImap ) )
|
||
|
return( -1 );
|
||
|
|
||
|
seq_num = 0;
|
||
|
memset( recv_buf, 0, sizeof( recv_buf ) );
|
||
|
|
||
|
for( i=0; i<MAX_BUFFER_LINES; i++ )
|
||
|
line[i] = NULL;
|
||
|
|
||
|
if ( recv( fdImap, (char *)recv_buf,
|
||
|
sizeof( recv_buf ), 0 ) == SOCKET_ERROR ) {
|
||
|
close( fdImap );
|
||
|
fdImap = (-1);
|
||
|
LogMessage( "Socket revc error", errno );
|
||
|
return( -1 );
|
||
|
}
|
||
|
|
||
|
if ( strncmp( "* OK", recv_buf, 4 ) == 0 ) {
|
||
|
return( 0 );
|
||
|
}
|
||
|
else {
|
||
|
close( fdImap );
|
||
|
LogMessage( "IMAP service timeout", 0 );
|
||
|
return( -1 );
|
||
|
}
|
||
|
|
||
|
} /* OpenImapSocket */
|
||
|
|
||
|
/****************************************************************************/
|
||
|
/*
|
||
|
* ImapCmd()
|
||
|
*
|
||
|
* Send an IMAP command to the socket. The "tag" is used by the IMAP
|
||
|
* protocol to match responses to commands.
|
||
|
*/
|
||
|
|
||
|
static int ImapCmd( char *fmt, ... )
|
||
|
{
|
||
|
char cmd_buf [128];
|
||
|
va_list argp;
|
||
|
|
||
|
sprintf( tag, "A%3.3d", ++seq_num );
|
||
|
sprintf( cmd_buf, "%s ", tag );
|
||
|
|
||
|
va_start( argp, fmt );
|
||
|
vsprintf( &cmd_buf[ strlen( cmd_buf ) ], fmt, argp );
|
||
|
va_end( argp );
|
||
|
|
||
|
if ( send( fdImap, cmd_buf, strlen( cmd_buf ), 0 ) == SOCKET_ERROR ) {
|
||
|
close( fdImap );
|
||
|
fdImap = (-1);
|
||
|
LogMessage( "IMAP send error", errno );
|
||
|
return( -1 );
|
||
|
}
|
||
|
|
||
|
return( 0 );
|
||
|
|
||
|
} /* ImapCmd */
|
||
|
|
||
|
/****************************************************************************/
|
||
|
/*
|
||
|
* GetImapMsg()
|
||
|
*
|
||
|
* Get an IMAP response and check for the tag and for the "OK".
|
||
|
*/
|
||
|
|
||
|
static int GetImapMsg( char *buf, int size )
|
||
|
{
|
||
|
char tmp [16];
|
||
|
|
||
|
memset( buf, 0, size );
|
||
|
|
||
|
if ( recv( fdImap, (char *)buf, size, 0 ) == SOCKET_ERROR ) {
|
||
|
close( fdImap );
|
||
|
fdImap = (-1);
|
||
|
LogMessage( "IMAP read error", errno );
|
||
|
return( -1 );
|
||
|
}
|
||
|
|
||
|
sprintf( tmp, "%s OK", tag );
|
||
|
|
||
|
if ( strstr( buf, tmp ) != NULL )
|
||
|
return( 0 );
|
||
|
else {
|
||
|
LogMessage( "IMAP command error", 0 );
|
||
|
return( -1 );
|
||
|
}
|
||
|
|
||
|
} /* GetImapMsg */
|
||
|
|
||
|
/****************************************************************************/
|
||
|
/*
|
||
|
* ServerLogin( void )
|
||
|
*
|
||
|
* Start the IMAP session to check for new mail
|
||
|
* RETURN: 0 for success or -1 for failure
|
||
|
*/
|
||
|
|
||
|
int ServerLogin( void )
|
||
|
{
|
||
|
int status = -1;
|
||
|
|
||
|
if ( !OpenImapSocket( &addrImap ) ) {
|
||
|
if ( !ImapCmd( "LOGIN %s %s\n", user, passwd ) ) {
|
||
|
if ( GetImapMsg( recv_buf, sizeof( recv_buf ) ) )
|
||
|
LogMessage( "IMAP LOGIN error", 0 );
|
||
|
else
|
||
|
status = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return( status );
|
||
|
|
||
|
} /* ServerLogin */
|
||
|
|
||
|
/****************************************************************************/
|
||
|
/*
|
||
|
* ServerLogout()
|
||
|
*
|
||
|
* Close the IMAP session
|
||
|
* RETURN: none
|
||
|
*/
|
||
|
|
||
|
void ServerLogout( void )
|
||
|
{
|
||
|
|
||
|
if ( !ImapCmd( "LOGOUT\n" ) )
|
||
|
GetImapMsg( recv_buf, sizeof( recv_buf ) );
|
||
|
|
||
|
close( fdImap );
|
||
|
|
||
|
} /* ServerLogout */
|
||
|
|
||
|
/****************************************************************************/
|
||
|
/*
|
||
|
* ParseBufLines()
|
||
|
*
|
||
|
* Parse the response into single lines.
|
||
|
*
|
||
|
* ARGS: b - raw response buffer
|
||
|
* l - an array char * for the individual lines
|
||
|
*
|
||
|
* RETURN: the number of lines
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
static int ParseBufLines( char *b, char *l[] )
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for ( i=0; i<MAX_BUFFER_LINES; i++ )
|
||
|
l[i] = NULL;
|
||
|
|
||
|
if ( (l[0] = strtok( b, "\n" )) == NULL )
|
||
|
return( 0 );
|
||
|
|
||
|
for ( i=1; i<MAX_BUFFER_LINES; i++ ) {
|
||
|
if ( (l[i] = strtok( NULL, "\n" )) == NULL )
|
||
|
return( i );
|
||
|
}
|
||
|
|
||
|
return( MAX_BUFFER_LINES );
|
||
|
|
||
|
} /* ParseBufLines */
|
||
|
|
||
|
/****************************************************************************/
|
||
|
/*
|
||
|
* CheckRecent()
|
||
|
*
|
||
|
* This routine looks through an array of lines for the "RECENT" response
|
||
|
* and returns the number of "RECENT" message. If there are no "RECENT"
|
||
|
* messages it then checks for any "UNSEEN" messages.
|
||
|
*
|
||
|
* ARGS: b - raw response buffer
|
||
|
* l - an array char * for the individual lines
|
||
|
*
|
||
|
* RETURN: number. 0 is no messages. For messages found with the
|
||
|
* RECENT command number is the count of RECENT messages. For
|
||
|
* messages found with the UNSEEN command 'number' is the
|
||
|
* message number of an UNSEEN message.
|
||
|
*/
|
||
|
|
||
|
static int CheckRecent( char *b, char *l[] )
|
||
|
{
|
||
|
int i;
|
||
|
int num_msg = -1;
|
||
|
|
||
|
/*
|
||
|
* Check for new messages that have arrived since the last time we looked
|
||
|
*/
|
||
|
if ( !GetImapMsg( recv_buf, sizeof( recv_buf ) ) ) {
|
||
|
if ( ParseBufLines( b, l ) > 0 ) {
|
||
|
for( i=0; i<MAX_BUFFER_LINES; i++ ) {
|
||
|
if ( strstr( l[i], "RECENT" ) != NULL )
|
||
|
break;
|
||
|
}
|
||
|
sscanf( l[i], "%*s %d %*s", &num_msg );
|
||
|
}
|
||
|
}
|
||
|
/*
|
||
|
* If nothing new has arrived check for any messages that may still be
|
||
|
* in the mailbox but have not been read
|
||
|
*/
|
||
|
if ( num_msg <= 0 ) {
|
||
|
if ( !ImapCmd( "SEARCH UNSEEN\n" ) ) {
|
||
|
if ( !GetImapMsg( recv_buf, sizeof( recv_buf ) ) ) {
|
||
|
if ( ParseBufLines( b, l ) > 0 ) {
|
||
|
for( i=0; i<MAX_BUFFER_LINES; i++ ) {
|
||
|
if ( strstr( l[i], "SEARCH" ) != NULL )
|
||
|
break;
|
||
|
}
|
||
|
sscanf( l[i], "%*s %*s %d", &num_msg );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
/*
|
||
|
* Any non-zero value means there are messages that have not been read.
|
||
|
* This is not a count or an index to the newest message
|
||
|
*/
|
||
|
return( num_msg );
|
||
|
|
||
|
} /* CheckRecent */
|
||
|
|
||
|
/****************************************************************************/
|
||
|
/*
|
||
|
* CheckInbox()
|
||
|
*
|
||
|
* This is the IMAP session to check for new mail
|
||
|
*
|
||
|
* RETURN: 1 for no messages, 0 if messages exist. /* Inverted by DAB */
|
||
|
*/
|
||
|
|
||
|
int CheckInbox( void )
|
||
|
{
|
||
|
int status = -1;
|
||
|
|
||
|
if ( fdImap < 0 ) {
|
||
|
if ( ServerLogin() )
|
||
|
return( !status ); /* Inverted by DAB */
|
||
|
}
|
||
|
|
||
|
if ( !ImapCmd( "EXAMINE INBOX\n" ) )
|
||
|
status = CheckRecent( recv_buf, line );
|
||
|
|
||
|
return( !status ); /* Inverted by DAB */
|
||
|
|
||
|
} /* CheckInbox */
|
||
|
|
||
|
/****************************************************************************/
|
||
|
/* end of imapsocket.c */
|