Re: Postfix version of smmapd

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Benjamin Donnachie wrote:
>> However, tests over telnet were fine and this patch against cyrus-imapd
>> v2.3.7 might be useful to someone...
> Ah, but it doesn't take care of this...

Tested over telnet and seems fine.  I will test it further with postfix
soon and probably tidy the code.

The mailbox test logic was taken from smmapd v.2.3.7 and may also
require further testing.

Ben

/*
 * Copyright (c) 1998-2003 Carnegie Mellon University.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The name "Carnegie Mellon University" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For permission or any other legal
 *    details, please contact  
 *      Office of Technology Transfer
 *      Carnegie Mellon University
 *      5000 Forbes Avenue
 *      Pittsburgh, PA  15213-3890
 *      (412) 268-4387, fax: (412) 268-7395
 *      tech-transfer@xxxxxxxxxxxxxx
 *
 * 4. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by Computing Services
 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
 *
 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * postmapd.c -- postfix local recipient table lookup.
 * Adapted by Ben Donnachie from smmapd.c -- sendmail socket map daemon
 *
 * Postfix table lookups can be directed to a TCP server.
 *
 * Request format:
 *  get SPACE key NEWLINE
 *	Lookup data under the specified key
 *  put SPACE key SPACE value NEWLINE
 *	Not currently implemented
 *
 * Reply format:
 *  500 SPACE text NEWLINE
 *	Requested data does not exist.  Text describes nature of problem.
 *  400 SPACE text NEWLINE
 *	Temporary error condition.  Text describes nature of problem.
 *  200 SPACE text NEWLINE
 *	Request was successful.  Text contains encoded version of data.
 *
 * $Id: postmapd.c,v 1.1.2.1 2007/01/16 05:05:31 benjamin Exp $
 *
 */

#include <config.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <signal.h>
#include <ctype.h>

#include "acl.h"
#include "append.h"
#include "mboxlist.h"
#include "global.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mupdate-client.h"
#include "util.h"
#include "xmalloc.h"

const char *BB;
int forcedowncase;

extern int optind;

struct protstream *map_in, *map_out;

/* current namespace */
static struct namespace map_namespace;

/* config.c info */
const int config_need_data = 0;

/* forward decls */
extern void setproctitle_init(int argc, char **argv, char **envp);
int begin_handling(void);

void shut_down(int code) __attribute__((noreturn));
void shut_down(int code)
{
    if (map_in) prot_free(map_in);
    if (map_out) prot_free(map_out);

    cyrus_reset_stdio(); 

    mboxlist_close();
    mboxlist_done();

    quotadb_close();
    quotadb_done();

    cyrus_done();
    exit(code);
}

void fatal(const char* s, int code)
{
    static int recurse_code = 0;
    if (recurse_code) {
        /* We were called recursively. Just give up */
	exit(code);
    }
    recurse_code = code;
    syslog(LOG_ERR, "Fatal error: %s", s);

    shut_down(code);
}

/*
 * run once when process is forked;
 * MUST NOT exit directly; must return with non-zero error code
 */
int service_init(int argc, char **argv, char **envp)
{
    int r;

    if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);

    setproctitle_init(argc, argv, envp);

    signals_set_shutdown(&shut_down);
    signal(SIGPIPE, SIG_IGN);

    BB = config_getstring(IMAPOPT_POSTUSER);
    forcedowncase = config_getswitch(IMAPOPT_LMTP_DOWNCASE_RCPT);

    /* so we can do mboxlist operations */
    mboxlist_init(0);
    mboxlist_open(NULL);

    /* so we can check the quotas */
    quotadb_init(0);
    quotadb_open(NULL);

    /* Set namespace */
    if ((r = mboxname_init_namespace(&map_namespace, 1)) != 0) {
	syslog(LOG_ERR, error_message(r));
	fatal(error_message(r), EC_CONFIG);
    }

    return 0;
}

/* Called by service API to shut down the service */
void service_abort(int error)
{
    shut_down(error);
}

int service_main(int argc __attribute__((unused)),
		 char **argv __attribute__((unused)),
		 char **envp __attribute__((unused)))
{
    int r; 

    map_in = prot_new(0, 0);
    map_out = prot_new(1, 1);
    prot_setflushonread(map_in, map_out);
    prot_settimeout(map_in, 360);

    r = begin_handling();

    shut_down(r);
    return 0;
}

int verify_user(const char *key, long quotacheck,
		struct auth_state *authstate)
{
    char rcpt[MAX_MAILBOX_NAME+1], namebuf[MAX_MAILBOX_NAME+1] = "";
    char *user = rcpt, *domain = NULL, *mailbox = NULL;
    int r = 0;

    /* make a working copy of the key and split it into user/domain/mailbox */
    strlcpy(rcpt, key, sizeof(rcpt));

    /* find domain */
    if (config_virtdomains && (domain = strrchr(rcpt, '@'))) {
	*domain++ = '\0';
	/* ignore default domain */
	if (config_defdomain && !strcasecmp(config_defdomain, domain))
	    domain = NULL;
    }

    /* translate any separators in user & mailbox */
    mboxname_hiersep_tointernal(&map_namespace, rcpt, 0);

    /* find mailbox */
    if ((mailbox = strchr(rcpt, '+'))) *mailbox++ = '\0';

    /* downcase the rcpt, if necessary */
    if (forcedowncase) {
	lcase(user);
	if (domain) lcase(domain);
    }

    /* see if its a shared mailbox address */
    if (!strcmp(user, BB)) user = NULL;

    /* XXX  the following is borrowed from lmtpd.c:verify_user() */
    if ((!user && !mailbox) ||
	(domain && (strlen(domain) + 1 > sizeof(namebuf)))) {
	r = IMAP_MAILBOX_NONEXISTENT;
    } else {
	/* construct the mailbox that we will verify */
	if (domain) snprintf(namebuf, sizeof(namebuf), "%s!", domain);

	if (!user) {
	    /* shared folder */
	    if (strlen(namebuf) + strlen(mailbox) > sizeof(namebuf)) {
 		r = IMAP_MAILBOX_NONEXISTENT;
	    } else {
		strlcat(namebuf, mailbox, sizeof(namebuf));
	    }
	} else {
	    /* ordinary user -- check INBOX */
	    if (strlen(namebuf) + 5 + strlen(user) > sizeof(namebuf)) {
		r = IMAP_MAILBOX_NONEXISTENT;
	    } else {
		strlcat(namebuf, "user.", sizeof(namebuf));
		strlcat(namebuf, user, sizeof(namebuf));
	    }
	}
    }

    if (!r) {
	long aclcheck = !user ? ACL_POST : 0;
	int type;
	char *acl;
        char *path;
        char *c;
        struct hostent *hp;
        char *host;
        struct sockaddr_in sin,sfrom;
        char buf[512];
        int soc, x, rc;

	/*
	 * check to see if mailbox exists and we can append to it:
	 *
	 * - must have posting privileges on shared folders
	 * - don't care about ACL on INBOX (always allow post)
	 * - don't care about message size (1 msg over quota allowed)
	 */
	r = mboxlist_detail(namebuf, &type, &path, NULL, NULL, &acl, NULL);
	if (r == IMAP_MAILBOX_NONEXISTENT && config_mupdate_server) {
	    kick_mupdate();
	    r = mboxlist_detail(namebuf, &type, &path, NULL, NULL, &acl, NULL);
	}

	if (!r && (type & MBTYPE_REMOTE)) {
	    /* XXX  Perhaps we should support the VRFY command in lmtpd
	     * and then we could do a VRFY to the correct backend which
	     * would also do a quotacheck.
	     */
	    int access = cyrus_acl_myrights(authstate, acl);

	    if ((access & aclcheck) != aclcheck) {
		r = (access & ACL_LOOKUP) ?
		    IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
                return r;
	    } else {
                r = 0;
            }

            /* proxy the request to the real backend server to 
             * check the quota.  if this fails, just return 0
             * (asuume under quota)
             */

            host = strdup(path);
            c = strchr(host, '!');
            if (c) *c = 0;

            syslog(LOG_ERR, "verify_user(%s) proxying to host %s",
                   namebuf, host);

            hp = gethostbyname(host);
            if (hp == (struct hostent*) 0) {
               syslog(LOG_ERR, "verify_user(%s) failed: can't find host %s",
                      namebuf, host);
               return r;
            }

            soc = socket(PF_INET, SOCK_STREAM, 0);
            memcpy(&sin.sin_addr.s_addr,hp->h_addr,hp->h_length);
            sin.sin_family = AF_INET;

            /* XXX port should be configurable */
            sin.sin_port = htons(12345);

            if (connect(soc,(struct sockaddr *) &sin, sizeof(sin)) < 0) { 
                syslog(LOG_ERR, "verify_user(%s) failed: can't connect to %s", 
                       namebuf, host);
               return r;
            }

            sprintf(buf,"%d:cyrus %s,%c",strlen(key)+6,key,4);
		// Does this get sent back to postfix?
            sendto(soc,buf,strlen(buf),0,(struct sockaddr *)&sin,sizeof(sin));

            x = sizeof(sfrom);
            rc = recvfrom(soc,buf,512,0,(struct sockaddr *)&sfrom,&x);
 
            buf[rc] = '\0';
            close(soc);

	    prot_printf(map_out, "%s", buf);
            return -1;   /* tell calling function we already replied */

	} else if (!r) {
	    r = append_check(namebuf, MAILBOX_FORMAT_NORMAL, authstate,
			     aclcheck, (quotacheck < 0 )
			     || config_getswitch(IMAPOPT_LMTP_STRICT_QUOTA) ?
			     quotacheck : 0);
	}
    }

    if (r) syslog(LOG_DEBUG, "verify_user(%s) failed: %s", namebuf,
		  error_message(r));

    return r;
}

#define MAXREQUEST 1024		/* XXX  is this reasonable? */

char hexchtoch(const char ch)
{
	char value = '\0';
	char chlower;

	// Assume sanity check already done

	chlower = tolower(ch);

	if (chlower >= '0' && chlower <= '9')
		value = chlower - '0';
	if (chlower >= 'a' && chlower <= 'f')
		value = 10 + (chlower - 'a');

	return value;
}

char * converttoalnumstr (char * str)
{
	// Remove any non printing characters from null terminated string.

	char * string;

	string = str;

	for (; *string != '\0'; string++) {
		if (!isalnum(*string)) {
			*string = '.';
		}
	}

	return str;

}

int begin_handling(void)
{

  while (SIGHUP != signals_poll()) {

        char request[MAXREQUEST+2];
        char *key = NULL;
        const char *errstring = NULL;
        int r = 0, length = 0;
	int writepos = 0, readpos = 0, count = 0;
        struct auth_state *authstate = NULL;

	if (0 == (length = prot_read (map_in, (char *) &request, 
		MAXREQUEST+1))) {
		fatal("Error reading stream", EC_IOERR);
	  }

	// VVV Tidy up to remove duplicate VVV

	if (SIGHUP == signals_poll()) {
		return 0;
	}

        if (length > MAXREQUEST || length < 0) {
                fatal("string too big", EC_IOERR);
	 }

	// Remove trailing \r \n from request.
	//  Any which form part of request will be encoded with %

	while (length > 1 && (request[length-1] == '\r' || 
		request[length-1] =='\n'))
		length--;

	request[length] = '\0';

	// Decode any % characters.

	for (readpos = 0, writepos = 0; (readpos <= length) && (!r); 
		readpos++, writepos++) {
		if ('%' == request[readpos]) {
			// Hex code follows.
			if (isxdigit(request[++readpos]) && 
				isxdigit(request[readpos+1])) {
				request[writepos] = 16 * 
					(hexchtoch(request[readpos++]));
				request[writepos] += 
					hexchtoch(request[readpos]);
			}
			else {
				errstring = "bad request";
				r = IMAP_PROTOCOL_ERROR;
			}

		}
		else {
			if (readpos != writepos) {
				request[writepos] = request[readpos];
			}
		}
				
        }

	// Separate command and key

        if (NULL == (key = strchr((char *) &request, ' '))) {
                errstring = "missing key";
                r = IMAP_PROTOCOL_ERROR;
        }

	else {
		*(key++) = '\0';
	}

	// lowercase command.

	for (count = 0; (!r) && request[count] != '\0'; count++) {
		if (isupper(request[count])) {
			request[count] = tolower(request[count]);
			}
		}

	if (!r && strcmp("get", request)) {
		errstring = "unknown request";
		r = IMAP_PROTOCOL_ERROR;
	}

	if (!r) {
		r = verify_user(key, strlen(key), authstate);
	}

	

	switch (r) {
        case -1:
            /* reply already sent */
            break;

	case 0:
	    prot_printf(map_out, "200 %s\r\n", converttoalnumstr (key));
	    break;

	case IMAP_MAILBOX_NONEXISTENT:
	    prot_printf(map_out, "500 %s\r\n", error_message(r));
	    break;

	case IMAP_QUOTA_EXCEEDED:
	    if (!config_getswitch(IMAPOPT_LMTP_OVER_QUOTA_PERM_FAILURE)) {
		prot_printf(map_out, "400 %s\r\n", error_message(r));
		break;
	    }
	    /* fall through - permanent failure */

	default:
	    if (errstring)
		prot_printf(map_out, "500 %s (%s)\r\n", error_message(r), errstring);
	    else
		prot_printf(map_out, "500 %s\r\n", error_message(r));
	    break;
	}

  }

  return 0;

}

void printstring(const char *s __attribute__((unused)))
{
    /* needed to link against annotate.o */
    fatal("printstring() executed, but its not used for smmapd!",
	  EC_SOFTWARE);
}

----
Cyrus Home Page: http://cyrusimap.web.cmu.edu/
Cyrus Wiki/FAQ: http://cyrusimap.web.cmu.edu/twiki
List Archives/Info: http://asg.web.cmu.edu/cyrus/mailing-list.html

[Index of Archives]     [Cyrus SASL]     [Squirrel Mail]     [Asterisk PBX]     [Video For Linux]     [Photo]     [Yosemite News]     [gtk]     [KDE]     [Gimp on Windows]     [Steve's Art]

  Powered by Linux