diff -ruN a/configure.ac b/configure.ac --- a/configure.ac 2007-01-04 15:35:45.000000000 -0500 +++ b/configure.ac 2007-01-21 08:38:53.000000000 -0500 @@ -251,6 +251,22 @@ AC_MSG_RESULT(root-mode pid file will go in $dir) AC_DEFINE_UNQUOTED(PID_DIR, "$dir", directory for PID lock files) +AC_ARG_ENABLE(pwmd, + [ --enable-pwmd enable Password Manager Daemon support], + , [enable_pwmd=no]) + +if test "$enable_pwmd" = "yes"; then + PKG_CHECK_EXISTS([libpwmd], have_libpwmd=1, + AC_MSG_ERROR([Could not find libpwmd pkg-config module.])) + + + PKG_CHECK_MODULES([libpwmd], [libpwmd >= 2.0.0]) + AM_CONDITIONAL(HAVE_LIBPWMD, true) + AC_DEFINE(HAVE_LIBPWMD, 1, [Define if you have libPWMD installed.]) +else + AM_CONDITIONAL(HAVE_LIBPWMD, false) +fi + # We may have a fallback MDA available in case the socket open to the # local SMTP listener fails. Best to use procmail for this, as we know # it won't try delivering through local SMTP and cause a mail loop. diff -ruN a/fetchmail.c b/fetchmail.c --- a/fetchmail.c 2006-12-18 19:15:52.000000000 -0500 +++ b/fetchmail.c 2007-01-21 08:35:13.000000000 -0500 @@ -145,6 +145,74 @@ const char *iana_charset; +#ifdef HAVE_LIBPWMD +static void exit_with_pwmd_error() +{ + if (pwmd_ret == PWMD_PERROR) { + fprintf(stderr, GT_("pwmd: %s\n"), pwmd_strerror(pwmd_error)); + exit(PS_AUTHFAIL); + } + else if (pwmd_ret == PWMD_AGENT_ERROR) { + fprintf(stderr, GT_("pwmd: gpg-agent error. Is GPG_AGENT_INFO set? Read gpg-agent(1) for mor info.\n")); + exit(PS_AUTHFAIL); + } + else + fprintf(stderr, GT_("pwmd: %s\n"), strerror(pwmd_error)); + + exit(PS_UNDEFINED); +} + +static void pwmd_set_agent_strings(const char *filename) +{ + /* + * Use gpg-agent for password retrieval. Since version 0.3 of pwmd + * there is an option to push files into the file cache when the + * server starts up. For a daemonized fetchmail, this may be + * required because there won't be a way to get the key without a + * tty. + */ + if ((pwmd_ret = pwmd_command(pwm, &pwmd_result, &pwmd_error, PWMD_SETOPT, + PWMD_OPTION_USEAGENT, 1)) != PWMD_OK) + exit_with_pwmd_error(); + + /* + * Set the text to be used in the pinentry dialog. + */ + if ((pwmd_ret = pwmd_command(pwm, &pwmd_result, &pwmd_error, PWMD_SETOPT, + PWMD_OPTION_TITLE, + "Password Manager Daemon: Fetchmail")) != PWMD_OK) + exit_with_pwmd_error(); + + snprintf(pwmd_buf, sizeof(pwmd_buf), + "A password is needed to open the file \"%s\". Please\n" + "enter the password below.", filename); + + if ((pwmd_ret = pwmd_command(pwm, &pwmd_result, &pwmd_error, PWMD_SETOPT, + PWMD_OPTION_DESC, pwmd_buf)) != PWMD_OK) + exit_with_pwmd_error(); +} + +static void pwmd_do_connect(const char *socketname, const char *filename) +{ + /* + * Try and connect to pwmd. + */ + if ((pwm = pwmd_connect(socketname, &pwmd_error)) == NULL) + exit_with_pwmd_error(); + + pwmd_set_agent_strings(filename); + + /* + * Try and open the file so we can later get account/server credentials. If + * the file is not cached on the server, gpg-agent will ask for the + * password. + */ + if ((pwmd_ret = pwmd_command(pwm, &pwmd_result, &pwmd_error, PWMD_OPEN, + filename)) != PWMD_OK) + exit_with_pwmd_error(); +} +#endif + int main(int argc, char **argv) { int bkgd = FALSE; @@ -275,6 +343,9 @@ #ifdef KERBEROS_V5 "+KRB5" #endif /* KERBEROS_V5 */ +#ifdef HAVE_LIBPWMD + "+PWMD" +#endif /* HAVE_LIBPWMD */ ".\n"; printf(GT_("This is fetchmail release %s"), VERSION); fputs(features, stdout); @@ -987,6 +1058,182 @@ #undef FLAG_MERGE } +#ifdef HAVE_LIBPWMD +static char *protocol_to_service(int protocol) +{ + switch (protocol) { +#ifdef POP2_ENABLE + case P_POP2: + return "POP2"; +#endif +#ifdef POP3_ENABLE + case P_POP3: + return "POP3"; +#endif +#ifdef IMAP_ENABLE + case P_IMAP: + return "IMAP"; +#endif + case P_APOP: + return "APOP"; + case P_RPOP: + return "RPOP"; + case P_ETRN: + return "ETRN"; + case P_ODMR: + return "ODMR"; + default: + return NULL; + } + + return NULL; +} + +static int pwmd_get_details(const char *pwmd_account, int protocol, + struct query *ctl) +{ + char *prot = protocol_to_service(protocol); + char *p; + + /* + * Get the hostname for this protocol. Element path must be + * account->[protocol]->hostname. + */ + snprintf(pwmd_buf, sizeof(pwmd_buf), + "GET %s\t%s\thostname\n", pwmd_account, prot); + + if ((pwmd_ret = pwmd_command(pwm, &pwmd_result, &pwmd_error, + PWMD_COMMAND, pwmd_buf)) != PWMD_OK) { + if (pwmd_ret == PWMD_PERROR && pwmd_error == EPWMD_ELEMENT_NOT_FOUND) { + fprintf(stderr, GT_("%s (hostname): %s\n"), pwmd_account, + pwmd_strerror(pwmd_error)); + exit(PS_SYNTAX); + } + else + exit_with_pwmd_error(); + } + + ctl->server.pollname = xstrdup(pwmd_account); + ctl->server.via = xstrdup(pwmd_result); + memset(pwmd_result, 0, strlen(pwmd_result)); + free(pwmd_result); + + /* + * Server port. Element path must be account->[protocol]->port. Should be + * required because the element path wouldn't exist with out it. But + * maybe not because fetchmail trys standard ports if not specified. This + * may be a security risk. Might be better to have 'ssl' required. + */ + snprintf(pwmd_buf, sizeof(pwmd_buf), "GET %s\t%s\tport\n", + pwmd_account, prot); + + if ((pwmd_ret = pwmd_command(pwm, &pwmd_result, &pwmd_error, + PWMD_COMMAND, pwmd_buf)) != PWMD_OK) { + if (pwmd_ret == PWMD_PERROR && pwmd_error == EPWMD_ELEMENT_NOT_FOUND) { + fprintf(stderr, GT_("%s (port): %s\n"), pwmd_account, + pwmd_strerror(pwmd_error)); + exit(PS_SYNTAX); + } + else + exit_with_pwmd_error(); + } + + p = xstrdup(pwmd_result); + ctl->server.service = p; + memset(pwmd_result, 0, strlen(pwmd_result)); + free(pwmd_result); + + /* + * Get the remote username. Element must be account->username. + */ + snprintf(pwmd_buf, sizeof(pwmd_buf), "GET %s\tusername\n", + pwmd_account); + + if ((pwmd_ret = pwmd_command(pwm, &pwmd_result, &pwmd_error, + PWMD_COMMAND, pwmd_buf)) != PWMD_OK) { + if (pwmd_ret == PWMD_PERROR && pwmd_error == EPWMD_ELEMENT_NOT_FOUND) + fprintf(stderr, GT_("%s (username): %s\n"), pwmd_account, + pwmd_strerror(pwmd_error)); + else + exit_with_pwmd_error(); + } + else { + ctl->remotename = xstrdup(pwmd_result); + ctl->server.esmtp_name = xstrdup(ctl->remotename); + memset(pwmd_result, 0, strlen(pwmd_result)); + free(pwmd_result); + } + + /* + * Get the remote password. Element must be account->password. + */ + snprintf(pwmd_buf, sizeof(pwmd_buf), "GET %s\tpassword\n", + pwmd_account); + + if ((pwmd_ret = pwmd_command(pwm, &pwmd_result, &pwmd_error, + PWMD_COMMAND, pwmd_buf)) != PWMD_OK) + if (pwmd_ret == PWMD_PERROR && pwmd_error == EPWMD_ELEMENT_NOT_FOUND) + fprintf(stderr, GT_("%s (password): %s\n"), pwmd_account, + pwmd_strerror(pwmd_error)); + else + exit_with_pwmd_error(); + else { + ctl->password= xstrdup(pwmd_result); + memset(pwmd_result, 0, strlen(pwmd_result)); + free(pwmd_result); + } + +#ifdef SSL_ENABLE + /* + * If there is a ssl element and set to 1, enable ssl for this account. + * Element path must be account->[protocol]->ssl. + */ + snprintf(pwmd_buf, sizeof(pwmd_buf), "GET %s\t%s\tssl\n", + pwmd_account, prot); + + if ((pwmd_ret = pwmd_command(pwm, &pwmd_result, &pwmd_error, + PWMD_COMMAND, pwmd_buf)) != PWMD_OK) { + if (pwmd_ret == PWMD_PERROR && pwmd_error == EPWMD_ELEMENT_NOT_FOUND) + fprintf(stderr, GT_("%s (ssl): %s\n"), pwmd_account, + pwmd_strerror(pwmd_error)); + else + exit_with_pwmd_error(); + } + else { + p = xstrdup(pwmd_result); + ctl->use_ssl = atoi(p); + ctl->use_ssl = (ctl->use_ssl >= 1) ? FLAG_TRUE : FLAG_FALSE; + memset(pwmd_result, 0, strlen(pwmd_result)); + free(pwmd_result); + free(p); + } + + /* + * account->[protocol]->sslfingerprint. + */ + snprintf(pwmd_buf, sizeof(pwmd_buf), + "GET %s\t%s\tsslfingerprint\n", pwmd_account, prot); + + if ((pwmd_ret = pwmd_command(pwm, &pwmd_result, &pwmd_error, + PWMD_COMMAND, pwmd_buf)) != PWMD_OK) { + if (pwmd_ret == PWMD_PERROR && pwmd_error == EPWMD_ELEMENT_NOT_FOUND) + fprintf(stderr, GT_("%s (sslfingerprint): %s\n"), pwmd_account, + pwmd_strerror(pwmd_error)); + else + exit_with_pwmd_error(); + } + else { + p = xstrdup(pwmd_result); + ctl->sslfingerprint = p; + memset(pwmd_result, 0, strlen(pwmd_result)); + free(pwmd_result); + } +#endif + + return 0; +} +#endif + /** Load configuration files. * \return - true if no servers found on the command line * - false if servers found on the command line */ @@ -1044,8 +1291,30 @@ if ((implicitmode = (optind >= argc))) { +#ifdef HAVE_LIBPWMD + for (ctl = querylist; ctl; ctl = ctl->next) { + ctl->active = !ctl->server.skip; + + if (ctl->pwmd_file) { + /* + * Cannot get an element path without a service. + */ + if (ctl->server.protocol <= 1) { + fprintf(stderr, GT_("fetchmail: %s configuration invalid, pwmd_file requires a protocol specification\n"), + ctl->server.pollname); + exit(PS_SYNTAX); + } + + pwmd_do_connect(ctl->pwmd_socket, ctl->pwmd_file); + pwmd_get_details(ctl->server.pollname, + ctl->server.protocol, ctl); + pwmd_close(pwm); + } + } +#else for (ctl = querylist; ctl; ctl = ctl->next) ctl->active = !ctl->server.skip; +#endif } else for (; optind < argc; optind++) @@ -1066,6 +1335,24 @@ fprintf(stderr,GT_("Warning: multiple mentions of host %s in config file\n"),argv[optind]); ctl->active = TRUE; predeclared = TRUE; + +#ifdef HAVE_LIBPWMD + if (ctl->pwmd_file) { + /* + * Cannot get an element path without a service. + */ + if (ctl->server.protocol <= 1) { + fprintf(stderr, GT_("fetchmail: %s configuration invalid, pwmd_file requires a protocol specification\n"), + ctl->server.pollname); + exit(PS_SYNTAX); + } + + pwmd_do_connect(ctl->pwmd_socket, ctl->pwmd_file); + pwmd_get_details(ctl->server.pollname, + ctl->server.protocol, ctl); + pwmd_close(pwm); + } +#endif } if (!predeclared) @@ -1076,8 +1363,29 @@ * call later on. */ ctl = hostalloc((struct query *)NULL); - ctl->server.via = - ctl->server.pollname = xstrdup(argv[optind]); + +#ifdef HAVE_LIBPWMD + if (cmd_opts.pwmd_file) { + /* + * Cannot get an element path without a service. + */ + if (cmd_opts.server.protocol == 0 || cmd_opts.server.protocol == 1) { + fprintf(stderr, GT_("Option --pwmd-file needs a service (-p) parameter.\n")); + exit(PS_SYNTAX); + } + + pwmd_do_connect(cmd_opts.pwmd_socket, cmd_opts.pwmd_file); + pwmd_get_details(argv[optind], cmd_opts.server.protocol, + ctl); + pwmd_close(pwm); + } + else + ctl->server.via = + ctl->server.pollname = xstrdup(argv[optind]); +#else + ctl->server.via = + ctl->server.pollname = xstrdup(argv[optind]); +#endif ctl->active = TRUE; ctl->server.lead_server = (struct hostdata *)NULL; } diff -ruN a/fetchmail.h b/fetchmail.h --- a/fetchmail.h 2006-12-15 19:42:14.000000000 -0500 +++ b/fetchmail.h 2007-01-21 08:35:13.000000000 -0500 @@ -39,6 +39,10 @@ # include "trio/trio.h" #endif +#ifdef HAVE_LIBPWMD +#include +#endif + /* We need this for strstr */ #if !defined(HAVE_STRSTR) && !defined(strstr) char *strstr(const char *, const char *); @@ -313,6 +317,11 @@ char *password; /* remote password to use */ struct idlist *mailboxes; /* list of mailboxes to check */ +#ifdef HAVE_LIBPWMD + char *pwmd_socket; /* socket to connect to */ + char *pwmd_file; /* file to open on the server */ +#endif + /* per-forwarding-target data */ struct idlist *smtphunt; /* list of SMTP hosts to try forwarding to */ struct idlist *domainlist; /* domainlist to fetch from */ @@ -455,6 +464,13 @@ extern char *sdps_envfrom; extern char *sdps_envto; #endif /* SDPS_ENABLE */ +#ifdef HAVE_LIBPWMD +pwm_t *pwm; /* the handle */ +char *pwmd_result; /* server result */ +int pwmd_error; /* protocol or errno */ +int pwmd_ret; /* return value from pwmd_command() */ +char pwmd_buf[LINE_MAX]; /* element copy buffer */ +#endif extern const char *iana_charset; /* IANA assigned charset name */ diff -ruN a/Makefile.am b/Makefile.am --- a/Makefile.am 2007-01-05 16:27:52.000000000 -0500 +++ b/Makefile.am 2007-01-21 08:35:13.000000000 -0500 @@ -18,6 +18,11 @@ pys= fetchmailconf.py pym= fetchmailconf.man +if HAVE_LIBPWMD +CFLAGS += @libpwmd_CFLAGS@ +LDFLAGS += @libpwmd_LIBS@ +endif + if HAVE_PYTHON nodist_bin_SCRIPTS= fetchmailconf python_PYTHON= $(pys) diff -ruN a/options.c b/options.c --- a/options.c 2006-08-14 19:04:02.000000000 -0400 +++ b/options.c 2007-01-21 08:35:13.000000000 -0500 @@ -53,12 +53,21 @@ LA_IDLE }; +#ifdef HAVE_LIBPWMD +static const char *shortoptions = + "C:G:?Vcsvd:NqL:f:i:p:UP:A:t:E:Q:u:akKFnl:r:S:Z:b:B:e:m:I:M:yw:D:"; +#else /* options still left: CgGhHjJoORTWxXYz */ static const char *shortoptions = "?Vcsvd:NqL:f:i:p:UP:A:t:E:Q:u:akKFnl:r:S:Z:b:B:e:m:I:M:yw:D:"; +#endif static const struct option longoptions[] = { /* this can be const because all flag fields are 0 and will never get set */ +#ifdef HAVE_LIBPWMD + {"pwmd-socket", required_argument, (int *) 0, 'C' }, + {"pwmd-file", required_argument, (int *) 0, 'G' }, +#endif {"help", no_argument, (int *) 0, '?' }, {"version", no_argument, (int *) 0, 'V' }, {"check", no_argument, (int *) 0, 'c' }, @@ -248,6 +257,14 @@ longoptions, &option_index)) != -1) { switch (c) { +#ifdef HAVE_LIBPWMD + case 'C': + ctl->pwmd_socket = prependdir(optarg, currentwd); + break; + case 'G': + ctl->pwmd_file = xstrdup(optarg); + break; +#endif case 'V': versioninfo = TRUE; break; @@ -618,6 +635,11 @@ P(GT_(" --plugout specify external command to open smtp connection\n")); P(GT_(" -p, --protocol specify retrieval protocol (see man page)\n")); +#ifdef HAVE_LIBPWMD + P(GT_(" -C, --pwmd-socket pwmd socket path (~/.pwmd/socket)\n")); + P(GT_(" -G, --pwmd-file filename to use on the pwmd server\n")); +#endif + P(GT_(" -U, --uidl force the use of UIDLs (pop3 only)\n")); P(GT_(" --port TCP port to connect to (obsolete, use --service)\n")); P(GT_(" -P, --service TCP service to connect to (can be numeric TCP port)\n")); diff -ruN a/rcfile_l.l b/rcfile_l.l --- a/rcfile_l.l 2006-08-14 19:04:02.000000000 -0400 +++ b/rcfile_l.l 2007-01-21 08:35:13.000000000 -0500 @@ -114,6 +114,8 @@ user(name)? {SETSTATE(NAME); return USERNAME; } +pwmd_socket { return PWMD_SOCKET; } +pwmd_file { return PWMD_FILE; } pass(word)? {SETSTATE(NAME); return PASSWORD; } folder(s)? { return FOLDER; } smtp(host)? { return SMTPHOST; } diff -ruN a/rcfile_y.y b/rcfile_y.y --- a/rcfile_y.y 2006-12-08 07:09:09.000000000 -0500 +++ b/rcfile_y.y 2007-01-21 08:44:03.000000000 -0500 @@ -63,7 +63,7 @@ %token DEFAULTS POLL SKIP VIA AKA LOCALDOMAINS PROTOCOL %token AUTHENTICATE TIMEOUT KPOP SDPS ENVELOPE QVIRTUAL -%token USERNAME PASSWORD FOLDER SMTPHOST FETCHDOMAINS MDA BSMTP LMTP +%token PWMD_SOCKET PWMD_FILE USERNAME PASSWORD FOLDER SMTPHOST FETCHDOMAINS MDA BSMTP LMTP %token SMTPADDRESS SMTPNAME SPAMRESPONSE PRECONNECT POSTCONNECT LIMIT WARNINGS %token INTERFACE MONITOR PLUGIN PLUGOUT %token IS HERE THERE TO MAP WILDCARD @@ -243,6 +243,20 @@ userdef : USERNAME STRING {current.remotename = xstrdup($2);} | USERNAME mapping_list HERE | USERNAME STRING THERE {current.remotename = xstrdup($2);} + | PWMD_SOCKET STRING { +#ifdef HAVE_LIBPWMD + current.pwmd_socket = xstrdup($2); +#else + yyerror(GT_("pwmd not enabled")); +#endif + } + | PWMD_FILE STRING { +#ifdef HAVE_LIBPWMD + current.pwmd_file = xstrdup($2); +#else + yyerror(GT_("pwmd not enabled")); +#endif + } ; user0opts : /* EMPTY */ diff -ruN a/README.pwmd b/README.pwmd --- a/README.pwmd 1969-12-31 19:00:00.000000000 -0500 +++ b/README.pwmd 2007-01-21 08:35:13.000000000 -0500 @@ -0,0 +1,33 @@ +When compiled with pwmd (Password Manager Daemon) support (--enable-pwmd), +fetchmail can retrieve server details from pwmd. Two new configuration +parameters are added: pwmd_socket, to specify the socket to connect to, and +pwmd_file (required) which specifies the filename on the server to open. + +The data that fetchmail will use is stored in an encrypted XML file and has +the following structure: + + + - Optional (--username/username) + - Optional (--password/password) + - POP3/IMAP/etc. + - Required (servername/via) + - Required (--service/protocol) + - Optional (--ssl/ssl) + - Optional (--sslfingerprint/sslfingerprint) + + + +A minimal fetchmailrc might look like this: + +poll isp proto POP3: + pwmd_file default + +Or from the command line: + fetchmail -f fetchmailrc isp + fetchmail --pwmd-file somefile -p POP3 isp + +The password to open the encrypted data file is either cached on the server +(the file has been opened before), or gotten from gpg-agent(1). + +Ben Kibbey +http://bjk.sourceforge.net/pwmd/.