/*
   +----------------------------------------------------------------------+
   | PHP Version 4                                                        |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997-2006 The PHP Group                                |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Author: Sterling Hughes <sterling@php.net>                           |
   +----------------------------------------------------------------------+
 */

/* $Id: cyrus.c,v 1.12.2.3.6.1 2006/01/01 13:46:51 sniper Exp $ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_cyrus.h"

#if HAVE_CYRUS

#include <cyrus/imclient.h>

#include <pwd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>

static int le_cyrus;
#define le_cyrus_name "Cyrus IMAP connection"

function_entry cyrus_functions[] = {
	PHP_FE(cyrus_connect,		NULL)
	PHP_FE(cyrus_authenticate,	NULL)
	PHP_FE(cyrus_bind,			NULL)
	PHP_FE(cyrus_unbind,		NULL)
	PHP_FE(cyrus_query,			NULL)
	PHP_FE(cyrus_close,			NULL)
	{NULL, NULL, NULL}
};

zend_module_entry cyrus_module_entry = {
	STANDARD_MODULE_HEADER,
	"cyrus",
	cyrus_functions,
	PHP_MINIT(cyrus),
	NULL,
	NULL,
	NULL,
	PHP_MINFO(cyrus),
	NO_VERSION_YET,
	STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_CYRUS
ZEND_GET_MODULE(cyrus)
#endif

static void cyrus_free(zend_rsrc_list_entry *rsrc)
{
	php_cyrus *conn = (php_cyrus *) rsrc->ptr;

	if (conn->client)
		imclient_close(conn->client);

	if (conn->host)
		efree(conn->host);

	if (conn->port)
		efree(conn->port);

	efree(conn);
}

PHP_MINIT_FUNCTION(cyrus)
{
	le_cyrus = zend_register_list_destructors_ex(cyrus_free, NULL,
	                                             le_cyrus_name, module_number);

	REGISTER_LONG_CONSTANT("CYRUS_CONN_NONSYNCLITERAL",
	                       IMCLIENT_CONN_NONSYNCLITERAL,
	                       CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("CYRUS_CONN_INITIALRESPONSE",
	                       IMCLIENT_CONN_INITIALRESPONSE,
	                       CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("CYRUS_CALLBACK_NUMBERED", CALLBACK_NUMBERED,
	                       CONST_CS | CONST_PERSISTENT);
	REGISTER_LONG_CONSTANT("CYRUS_CALLBACK_NOLITERAL", CALLBACK_NOLITERAL,
	                       CONST_CS | CONST_PERSISTENT);

	return SUCCESS;
}

PHP_MINFO_FUNCTION(cyrus)
{
	php_info_print_table_start();
	php_info_print_table_row(2, "Cyrus IMAP support", "enabled");
	php_info_print_table_end();
}

extern void fatal(char *s, int exit)
{
	php_error(E_ERROR, "%s", s);
}

/* {{{ proto resource cyrus_connect([ string host [, string port [, int flags]]])
   Connect to a Cyrus IMAP server */
PHP_FUNCTION(cyrus_connect)
{
	zval            **z_host;
	zval            **z_port;
	zval            **z_flags;
	php_cyrus        *conn;
	struct imclient  *client;
	char             *host;
	char             *port = NULL;
	int               flags = 0;
	int               error;
	int               argc = ZEND_NUM_ARGS();

	if (argc < 0 || argc > 3 ||
	    zend_get_parameters_ex(argc, &z_host, &z_port, &z_flags) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	if (argc > 0) {
		convert_to_string_ex(z_host);
		host = estrndup(Z_STRVAL_PP(z_host), Z_STRLEN_PP(z_host));
	}
	else {
		host = estrndup("localhost", sizeof("localhost") - 1);
	}

	if (argc > 1) {
		convert_to_string_ex(z_port);
		port = estrndup(Z_STRVAL_PP(z_port), Z_STRLEN_PP(z_port));
	}

	if (argc > 2) {
		convert_to_long_ex(z_flags);
		flags = Z_LVAL_PP(z_flags);
	}

	error = imclient_connect(&client, host, port, NULL);
	switch (error) {
	case 0:
		if (client) {
			conn = ecalloc(1, sizeof *conn);
			conn->client = client;
			conn->host = host;
			conn->port = port;

			if (flags) {
				imclient_setflags(conn->client, flags);
				conn->flags = flags;
			}
		}

		break;

	case -1:
		php_error(E_WARNING, "%s(): Invalid hostname: %s", get_active_function_name(TSRMLS_C), host);
		RETURN_FALSE;

	case -2:
		php_error(E_WARNING, "%s(): Invalid port: %d", get_active_function_name(TSRMLS_C), port);
		RETURN_FALSE;
	}

	ZEND_REGISTER_RESOURCE(return_value, conn, le_cyrus);
	conn->id = Z_LVAL_P(return_value);
}
/* }}} */


static void cyrus_capable_callback(struct imclient *client, void *rock,
                                   struct imclient_reply *reply)
{
	char *token = NULL;
	char *token_buf;
	char *mechanism = rock;


	/* We need to split the reply up by the whitespace */
	token = php_strtok_r(reply->text, " ", &token_buf);
	while (token != NULL) {
		if (! strncmp(token, "AUTH=", 5)) {
			memcpy(mechanism, token + 5, strlen(token) - 5);
			break;
		}

		token = php_strtok_r(NULL, " ", &token_buf);
	}
}


/* {{{ proto void cyrus_authenticate( resource connection [, string mechlist [, string service [, string user [, int minssf [, int maxssf]]]]])
   Authenticate agaings a Cyrus IMAP server */
PHP_FUNCTION(cyrus_authenticate)
{
	zval        **z_conn;
	zval        **z_mechlist;
	zval        **z_service;
	zval        **z_user;
	zval        **z_minssf;
	zval        **z_maxssf;
	php_cyrus    *conn;
	char         *mechlist;
	char         *service;
	char         *user;
	int           minssf;
	int           maxssf;
	int           argc = ZEND_NUM_ARGS();

	if (argc < 1 || argc > 6 ||
	    zend_get_parameters_ex(argc, &z_conn, &z_mechlist, &z_service, &z_user, &z_minssf, &z_maxssf) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	ZEND_FETCH_RESOURCE(conn, php_cyrus *, z_conn, -1, le_cyrus_name, le_cyrus);

	/* Determine support mechanisms */
	if (argc > 1 && Z_TYPE_PP(z_mechlist) != IS_NULL) {
		convert_to_string_ex(z_mechlist);

		mechlist = estrndup(Z_STRVAL_PP(z_mechlist), Z_STRLEN_PP(z_mechlist));
	}
	else {
		char tmp_mechlist[100];
		int  pos = 0;

		/* NULL out the buffer, ensures it has a safe ending and allows us to
		 * test properly for the end of the buffer
		 */
		memset(tmp_mechlist, 0, sizeof tmp_mechlist);

		/* We'll be calling the "CAPABILITY" command, which will give us a list
		 * of the types of authorization the server is capable of
		 */
		imclient_addcallback(conn->client, "CAPABILITY", 0,
		                     cyrus_capable_callback, (void *) tmp_mechlist, 0);
		imclient_send(conn->client, NULL, NULL, "CAPABILITY");

		/* Grab the end of string position into pos */
		while (tmp_mechlist[pos++] != 0)
			;

		/* Tack on PLAIN to whatever the auth string is */
		memcpy(tmp_mechlist + pos, " PLAIN", 6);

		/* Copy it onto the main buffer */
		mechlist = estrndup(tmp_mechlist, pos + 6);
	}

	/* Determine the service type */
	if (argc > 2 && Z_TYPE_PP(z_service) != IS_NULL) {
		convert_to_string_ex(z_service);
		service = estrndup(Z_STRVAL_PP(z_service), Z_STRLEN_PP(z_service));
	}
	else {
		service = estrndup("imap", 4);
	}

	/* Determine the user */
	if (argc > 3 && Z_TYPE_PP(z_user) != IS_NULL) {
		convert_to_string_ex(z_user);
		user = estrndup(Z_STRVAL_PP(z_user), Z_STRLEN_PP(z_user));
	}
	else {
		/* XXX: UGLY, but works, determines the username to use */
		user = (char *) sapi_getenv("USER", 4);
		if (! user) {
			user = (char *) getenv("USER");
			if (! user) {
				user = (char *) sapi_getenv("LOGNAME", 7);
				if (! user) {
				user = (char *) getenv("LOGNAME");
					if (! user) {
						struct passwd *pwd = getpwuid(getuid());
						if (! pwd) {
							php_error(E_WARNING, "%s(): Couldn't determine user id", get_active_function_name(TSRMLS_C));
							RETURN_FALSE;
						}
		
						user = estrdup(pwd->pw_name);
					}
				}
			}
		}
	}

	/* Determine the minssf */
	if (argc > 4 && Z_TYPE_PP(z_minssf) != NULL) {
		convert_to_long_ex(z_minssf);
		minssf = Z_LVAL_PP(z_minssf);
	}
	else {
		minssf = 0;
	}

	/* Determine the maxssf */
	if (argc > 5 && Z_TYPE_PP(z_maxssf) != NULL) {
		convert_to_long_ex(z_maxssf);
		maxssf = Z_LVAL_PP(z_maxssf);
	}
	else {
		maxssf = 1000;
	}

	imclient_authenticate(conn->client, mechlist, service,
	                      user, minssf, maxssf);

	efree(mechlist);
	efree(service);
	efree(user);
}
/* }}} */


static void cyrus_generic_callback(struct imclient *client,
                                   void *rock,
                                   struct imclient_reply *reply)
{
	php_cyrus_callback *callback = rock;

	if (client) {
		zval **argv[4];
		zval  *retval;
		zval  *cyrus;
		zval  *keyword;
		zval  *text;
		zval  *msgno;
		TSRMLS_FETCH();

		MAKE_STD_ZVAL(cyrus);
		MAKE_STD_ZVAL(keyword);
		MAKE_STD_ZVAL(text);
		MAKE_STD_ZVAL(msgno);

		ZVAL_RESOURCE(cyrus, callback->le);
		zend_list_addref(callback->le);

		ZVAL_STRING(keyword, reply->keyword, 1);
		ZVAL_STRING(text, reply->text, 1);
		ZVAL_LONG(msgno, reply->msgno);

		argv[0] = &cyrus;
		argv[1] = &keyword;
		argv[2] = &text;
		argv[3] = &msgno;

		if (call_user_function_ex(EG(function_table), NULL, callback->function,
                                  &retval, 4, argv, 0, NULL TSRMLS_CC) == FAILURE) {
			php_error(E_WARNING, "%s(): Couldn't call the %s handler",
			          get_active_function_name(TSRMLS_C), callback->trigger);
		}

		zval_ptr_dtor(argv[0]);
		zval_ptr_dtor(argv[1]);
		zval_ptr_dtor(argv[2]);
		zval_ptr_dtor(argv[3]);

		efree(argv);
	}
	else {
		return;
	}
}


/* {{{ proto bool cyrus_bind( resource connection, array callbacks)
 Bind callbacks to a Cyrus IMAP connection */
PHP_FUNCTION(cyrus_bind)
{
	zval                  **z_conn;
	zval                  **z_callback;
	zval                  **tmp;
	HashTable              *hash;
	php_cyrus             *conn;
	php_cyrus_callback     callback;
	char                  *string_key;
	ulong                  num_key;

	if (ZEND_NUM_ARGS() != 2 ||
	    zend_get_parameters_ex(2, &z_conn, &z_callback) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	ZEND_FETCH_RESOURCE(conn, php_cyrus *, z_conn, -1, le_cyrus_name, le_cyrus);

	hash = HASH_OF(*z_callback);
	if (! hash) {
		php_error(E_WARNING,
		          "%s(): Second argument must be an array or object", get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}

	for (zend_hash_internal_pointer_reset(hash);
	     zend_hash_get_current_data(hash, (void **) &tmp) == SUCCESS;
		 zend_hash_move_forward(hash)) {
		SEPARATE_ZVAL(tmp);
		zend_hash_get_current_key(hash, &string_key, &num_key, 0);
		if (! string_key)
			continue;

		if (! strcasecmp(string_key, "trigger")) {
			convert_to_string_ex(tmp);
			callback.trigger = estrndup(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp));
		}
		else if (! strcasecmp(string_key, "function")) {
			callback.function = *tmp;
			zval_add_ref(&callback.function);
		}
		else if (! strcasecmp(string_key, "flags")) {
			convert_to_long_ex(tmp);
			callback.flags |= Z_LVAL_PP(tmp);
		}
	}

	if (! callback.trigger) {
		php_error(E_WARNING, "%s(): You must specify a trigger in your callback", get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}

	if (! callback.function) {
		php_error(E_WARNING, "%s(): You must specify a function in your callback", get_active_function_name(TSRMLS_C));
		RETURN_FALSE;
	}

	callback.le = conn->id;

	imclient_addcallback(conn->client, callback.trigger, callback.flags,
	                     cyrus_generic_callback, (void **) &callback, 0);

	RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool cyrus_unbind( resource connection, string trigger_name)
   Unbind ... */
PHP_FUNCTION(cyrus_unbind)
{
	zval        **z_conn;
	zval        **trigger_name;
	php_cyrus    *conn;

	if (ZEND_NUM_ARGS() != 2 ||
	    zend_get_parameters_ex(2, &z_conn, &trigger_name) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	ZEND_FETCH_RESOURCE(conn, php_cyrus *, z_conn, -1, le_cyrus_name, le_cyrus);
	convert_to_string_ex(trigger_name);

	imclient_addcallback(conn->client, Z_STRVAL_PP(trigger_name), 0,
	                     NULL, NULL, 0);

	RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool cyrus_query( resource connection, string query)
   Send a query to a Cyrus IMAP server */
PHP_FUNCTION(cyrus_query)
{
	zval               **z_conn;
	zval               **query;
	php_cyrus           *conn;

	if (ZEND_NUM_ARGS() != 2 ||
	    zend_get_parameters_ex(2, &z_conn, &query) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	ZEND_FETCH_RESOURCE(conn, php_cyrus *, z_conn, -1, le_cyrus_name, le_cyrus);
	convert_to_string_ex(query);

	imclient_send(conn->client, NULL, NULL, Z_STRVAL_PP(query));

	RETURN_TRUE;
}
/* }}} */

/* {{{ proto bool cyrus_close( resource connection)
   Close connection to a cyrus server */
PHP_FUNCTION(cyrus_close)
{
	zval      **z_conn;
	php_cyrus  *conn;

	if (ZEND_NUM_ARGS() != 1 ||
	    zend_get_parameters_ex(1, &z_conn) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	ZEND_FETCH_RESOURCE(conn, php_cyrus *, z_conn, -1, le_cyrus_name, le_cyrus);

	zend_list_delete(Z_LVAL_PP(z_conn));

	RETURN_TRUE;
}
/* }}} */
#endif


/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * indent-tabs-mode: t
 * End:
 */
