dockapps/wmbiff/wmbiff/Imap4Client.c

728 lines
20 KiB
C

/* rewrite of the IMAP code by Neil Spring
* (nspring@cs.washington.edu) to support gnutls and
* persistent connections to servers. */
/* Originally written by Yong-iL Joh (tolkien@mizi.com),
* modified by Jorge Garcia (Jorge.Garcia@uv.es), and
* modified by Jay Francis (jtf@u880.org) to support
* CRAM-MD5 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "Client.h"
#include "charutil.h"
#include "tlsComm.h"
#include "passwordMgr.h"
#include "regulo.h"
#include "MessageList.h"
#include <sys/types.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <ctype.h>
#include <strings.h>
#ifdef USE_DMALLOC
#include <dmalloc.h>
#endif
#define PCU (pc->u).pop_imap
extern int Relax;
#define IMAP_DM(pc, lvl, args...) DM(pc, lvl, "imap4: " args)
#ifdef HAVE_MEMFROB
#define DEFROB(x) memfrob(x, x ## _len)
#define ENFROB(x) memfrob(x, x ## _len)
#else
#define DEFROB(x)
#define ENFROB(x)
#endif
/* this array maps server:port pairs to file descriptors, so
that when more than one mailbox is queried from a server,
we only use one socket. It's limited in size by the
number of different mailboxes displayed. */
#define FDMAP_SIZE 5
static struct fdmap_struct {
char *user_server_port; /* tuple, in string form */
/*@owned@ */ struct connection_state *cs;
} fdmap[FDMAP_SIZE];
static void ask_user_for_password( /*@notnull@ */ Pop3 pc,
int bFlushCache);
/* authentication callbacks */
#ifdef HAVE_GCRYPT_H
static int authenticate_md5( /*@notnull@ */ Pop3 pc,
struct connection_state *scs,
const char *capabilities);
#endif
static int authenticate_plaintext( /*@notnull@ */ Pop3 pc,
struct connection_state *scs,
const char *capabilities);
/* the auth_methods array maps authentication identifiers
to the callback that will attempt to authenticate */
static struct imap_authentication_method {
const char *name;
/* callback returns 1 if successful, 0 if failed */
int (*auth_callback) ( /*@notnull@ */ Pop3 pc,
struct connection_state * scs,
const char *capabilities);
} auth_methods[] = {
{
#ifdef HAVE_GCRYPT_H
"cram-md5", authenticate_md5}, {
#endif
"plaintext", authenticate_plaintext}, {
NULL, NULL}
};
/* recover a socket from the connection cache */
/*@null@*/
/*@dependent@*/
static struct connection_state *state_for_pcu(Pop3 pc)
{
char *connection_id;
struct connection_state *retval = NULL;
int i;
connection_id =
malloc(strlen(PCU.userName) + strlen(PCU.serverName) + 22);
sprintf(connection_id, "%s|%s|%d", PCU.userName, PCU.serverName,
PCU.serverPort);
for (i = 0; i < FDMAP_SIZE; i++)
if (fdmap[i].user_server_port != NULL &&
(strcmp(connection_id, fdmap[i].user_server_port) == 0)) {
retval = fdmap[i].cs;
}
free(connection_id);
return (retval);
}
/* bind to the connection cache */
static void bind_state_to_pcu(Pop3 pc,
/*@owned@ */ struct connection_state *scs)
{
char *connection_id;
int i;
if (scs == NULL) {
abort();
}
connection_id =
malloc(strlen(PCU.userName) + strlen(PCU.serverName) + 22);
sprintf(connection_id, "%s|%s|%d", PCU.userName, PCU.serverName,
PCU.serverPort);
for (i = 0; i < FDMAP_SIZE && fdmap[i].cs != NULL; i++);
if (i == FDMAP_SIZE) {
/* should never happen */
IMAP_DM(pc, DEBUG_ERROR,
"Tried to open too many IMAP connections. Sorry!\n");
exit(EXIT_FAILURE);
}
fdmap[i].user_server_port = connection_id;
fdmap[i].cs = scs;
}
/* remove from the connection cache */
static
/*@owned@*/
/*@null@*/
struct connection_state *unbind(
/*@returned@*/ struct connection_state
*scs)
{
int i;
struct connection_state *retval = NULL;
assert(scs != NULL);
for (i = 0; i < FDMAP_SIZE && fdmap[i].cs != scs; i++);
if (i < FDMAP_SIZE) {
free(fdmap[i].user_server_port);
fdmap[i].user_server_port = NULL;
retval = fdmap[i].cs;
fdmap[i].cs = NULL;
}
return (retval);
}
/* creates a connection to the server, if a matching one doesn't exist. */
/* *always* returns null, just declared this wasy to match other protocols. */
/*@null@*/
FILE *imap_open(Pop3 pc)
{
static int complained_already; /* we have to succeed once before
complaining again about failure */
struct connection_state *scs;
struct imap_authentication_method *a;
char *connection_name;
int sd;
char capabilities[BUF_SIZE];
char buf[BUF_SIZE];
if (state_for_pcu(pc) != NULL) {
/* don't need to open. */
return NULL;
}
/* got this far; we're going to create a connection_state
structure, although it might be a blacklist entry */
connection_name = malloc(strlen(PCU.serverName) + 20);
sprintf(connection_name, "%s:%d", PCU.serverName, PCU.serverPort);
assert(pc != NULL);
/* no cached connection */
sd = sock_connect((const char *) PCU.serverName, PCU.serverPort);
if (sd == -1) {
if (complained_already == 0) {
IMAP_DM(pc, DEBUG_ERROR, "Couldn't connect to %s:%d: %s\n",
PCU.serverName, PCU.serverPort,
errno ? strerror(errno) : "");
complained_already = 1;
}
if (errno == ETIMEDOUT) {
/* temporarily bump the interval, in a crude way:
fast forward time so that the mailbox isn't
checked for a while. */
pc->prevtime = time(0) + 60 * 5; /* now + 60 seconds per min * 5 minutes */
/* TCP's retry (how much time has elapsed while
the connect times out) is around 3 minutes;
here we just try to allow checking local
mailboxes more often while remote things are
unavailable or disconnected. */
}
return NULL;
}
/* build the connection using STARTTLS */
if (PCU.dossl != 0 && (PCU.serverPort == 143)) {
/* setup an unencrypted binding long enough to invoke STARTTLS */
scs = initialize_unencrypted(sd, connection_name, pc);
/* can we? */
tlscomm_printf(scs, "a000 CAPABILITY\r\n");
if (tlscomm_expect(scs, "* CAPABILITY", capabilities, BUF_SIZE) ==
0)
goto communication_failure;
if (!strstr(capabilities, "STARTTLS")) {
IMAP_DM(pc, DEBUG_ERROR,
"server doesn't support ssl imap on port 143.");
goto communication_failure;
}
/* we sure can! */
IMAP_DM(pc, DEBUG_INFO, "Negotiating TLS within IMAP");
tlscomm_printf(scs, "a001 STARTTLS\r\n");
if (tlscomm_expect(scs, "a001 ", buf, BUF_SIZE) == 0)
goto communication_failure;
if (strstr(buf, "a001 OK") == 0) {
/* we didn't see the success message in the response */
IMAP_DM(pc, DEBUG_ERROR, "couldn't negotiate tls. :(\n");
goto communication_failure;
}
/* we don't need the unencrypted state anymore */
/* note that communication_failure will close the
socket and free via tls_close() */
free(scs); /* fall through will scs = initialize_gnutls(sd); */
}
/* either we've negotiated ssl from starttls, or
we're starting an encrypted connection now */
if (PCU.dossl != 0) {
scs = initialize_gnutls(sd, connection_name, pc, PCU.serverName);
if (scs == NULL) {
IMAP_DM(pc, DEBUG_ERROR, "Failed to initialize TLS\n");
return NULL;
}
} else {
scs = initialize_unencrypted(sd, connection_name, pc);
}
/* authenticate; first find out how */
/* note that capabilities may have changed since last
time we may have asked, if we called STARTTLS, my
server will allow plain password login within an
encrypted session. */
tlscomm_printf(scs, "a000 CAPABILITY\r\n");
if (tlscomm_expect(scs, "* CAPABILITY", capabilities, BUF_SIZE) == 0) {
IMAP_DM(pc, DEBUG_ERROR, "unable to query capability string");
goto communication_failure;
}
/* try each authentication method in turn. */
for (a = auth_methods; a->name != NULL; a++) {
/* was it specified or did the user leave it up to us? */
if (PCU.authList[0] == '\0'
|| strstr(PCU.authList, a->name) != NULL)
/* try the authentication method */
if ((a->auth_callback(pc, scs, capabilities)) != 0) {
/* store this well setup connection in the cache */
bind_state_to_pcu(pc, scs);
complained_already = 0;
return NULL;
}
}
/* if authentication worked, we won't get here */
IMAP_DM(pc, DEBUG_ERROR,
"All authentication methods failed for '%s@%s:%d'\n",
PCU.userName, PCU.serverName, PCU.serverPort);
communication_failure:
tlscomm_printf(scs, "a002 LOGOUT\r\n");
tlscomm_close(scs);
return NULL;
}
void imap_cacheHeaders( /*@notnull@ */ Pop3 pc);
int imap_checkmail( /*@notnull@ */ Pop3 pc)
{
/* recover connection state from the cache */
struct connection_state *scs = state_for_pcu(pc);
char buf[BUF_SIZE];
static int command_id;
/* if it's not in the cache, try to open */
if (scs == NULL) {
IMAP_DM(pc, DEBUG_INFO, "Need new connection to %s@%s\n",
PCU.userName, PCU.serverName);
(void) imap_open(pc);
scs = state_for_pcu(pc);
}
if (scs == NULL) {
return -1;
}
if (tlscomm_is_blacklisted(scs) != 0) {
/* unresponsive server, don't bother. */
return -1;
}
/* if we've got it by now, try the status query */
command_id++;
tlscomm_printf(scs, "a%03d STATUS %s (MESSAGES UNSEEN)\r\n",
command_id % 1000, pc->path);
if (tlscomm_expect(scs, "* STATUS", buf, 127) != 0) {
/* a valid response? */
// doesn't support spaces: (void) sscanf(buf, "* STATUS %*s (MESSAGES %d UNSEEN %d)",
const char *msg;
msg = strstr(buf, "(MESSAGES");
if (msg != NULL)
(void) sscanf(msg, "(MESSAGES %d UNSEEN %d)",
&(pc->TotalMsgs), &(pc->UnreadMsgs));
/* update the cached headers if evidence that change
has occurred; not necessarily complete. */
if (pc->UnreadMsgs != pc->OldUnreadMsgs ||
pc->TotalMsgs != pc->OldMsgs) {
if (PCU.wantCacheHeaders) {
imap_cacheHeaders(pc);
}
}
} else {
/* something went wrong. bail. */
tlscomm_close(unbind(scs));
return -1;
}
return 0;
}
void
imap_releaseHeaders(Pop3 pc __attribute__ ((unused)), struct msglst *h)
{
assert(h != NULL);
/* allow the list to be released next time around */
if (h->in_use <= 0) {
/* free the old one */
while (h != NULL) {
struct msglst *n = h->next;
free(h);
h = n;
}
} else {
h->in_use--;
}
}
void imap_cacheHeaders( /*@notnull@ */ Pop3 pc)
{
struct connection_state *scs = state_for_pcu(pc);
char *msgid;
char buf[BUF_SIZE];
if (scs == NULL) {
(void) imap_open(pc);
scs = state_for_pcu(pc);
}
if (scs == NULL) {
return;
}
if (tlscomm_is_blacklisted(scs) != 0) {
return;
}
if (pc->headerCache != NULL) {
/* decrement the reference count, and free our version */
imap_releaseHeaders(pc, pc->headerCache);
pc->headerCache = NULL;
}
IMAP_DM(pc, DEBUG_INFO, "working headers\n");
tlscomm_printf(scs, "a004 EXAMINE %s\r\n", pc->path);
if (tlscomm_expect(scs, "a004 OK", buf, 127) == 0) {
tlscomm_close(unbind(scs));
return;
}
IMAP_DM(pc, DEBUG_INFO, "examine ok\n");
/* if we've got it by now, try the status query */
tlscomm_printf(scs, "a005 SEARCH UNSEEN\r\n");
if (tlscomm_expect(scs, "* SEARCH", buf, 127) == 0) {
tlscomm_close(unbind(scs));
return;
}
IMAP_DM(pc, DEBUG_INFO, "search: %s", buf);
if (strlen(buf) < 9)
return; /* search turned up nothing */
msgid = strtok(buf + 9, " \r\n");
pc->headerCache = NULL;
/* the isdigit cruft is to deal with EOL */
if (msgid != NULL && isdigit(msgid[0]))
do {
struct msglst *m = malloc(sizeof(struct msglst));
char hdrbuf[128];
int fetch_command_done = FALSE;
tlscomm_printf(scs, "a04 FETCH %s (FLAGS "
"BODY[HEADER.FIELDS (FROM SUBJECT)])\r\n",
msgid);
if (tlscomm_expect(scs, "* ", hdrbuf, 127)) {
m->subj[0] = '\0';
m->from[0] = '\0';
while (m->subj[0] == '\0' || m->from[0] == '\0') {
if (tlscomm_expect(scs, "", hdrbuf, 127)) {
if (strncasecmp(hdrbuf, "Subject:", 8) == 0) {
strncpy(m->subj, hdrbuf + 9, SUBJ_LEN - 1);
m->subj[SUBJ_LEN - 1] = '\0';
} else if (strncasecmp(hdrbuf, "From: ", 5) == 0) {
strncpy(m->from, hdrbuf + 6, FROM_LEN - 1);
m->from[FROM_LEN - 1] = '\0';
} else if (strncasecmp
(hdrbuf, "a04 OK FETCH", 5) == 0) {
/* server says we're done getting this header, which
may occur if the message has no subject */
if (m->from[0] == '\0') {
strcpy(m->from, " ");
}
if (m->subj[0] == '\0') {
strcpy(m->subj, "(no subject)");
}
fetch_command_done = TRUE;
}
} else {
IMAP_DM(pc, DEBUG_ERROR,
"timedout looking for headers.: %s",
hdrbuf);
strcpy(m->from, "wmbiff");
strcpy(m->subj, "failure");
}
}
IMAP_DM(pc, DEBUG_INFO, "From: '%s' Subj: '%s'\n",
m->from, m->subj);
m->next = pc->headerCache;
pc->headerCache = m;
pc->headerCache->in_use = 0; /* initialize that it isn't locked */
} else {
IMAP_DM(pc, DEBUG_ERROR, "error fetching: %s", hdrbuf);
}
if (!fetch_command_done) {
tlscomm_expect(scs, "a04 OK", hdrbuf, 127);
}
}
while ((msgid = strtok(NULL, " \r\n")) != NULL
&& isdigit(msgid[0]));
tlscomm_printf(scs, "a06 CLOSE\r\n"); /* return to polling state */
/* may be unneeded tlscomm_expect(scs, "a06 OK CLOSE\r\n" ); see if it worked? */
IMAP_DM(pc, DEBUG_INFO, "worked headers\n");
}
/* a client is asking for the headers, hand em a reference, increase the
one-bit reference counter */
struct msglst *imap_getHeaders( /*@notnull@ */ Pop3 pc)
{
if (pc->headerCache == NULL)
imap_cacheHeaders(pc);
if (pc->headerCache != NULL)
pc->headerCache->in_use = 1;
return pc->headerCache;
}
/* parse the config line to setup the Pop3 structure */
int imap4Create( /*@notnull@ */ Pop3 pc, const char *const str)
{
int i;
int matchedchars;
/* special characters aren't allowed in hostnames, rfc 1034 */
const char *regexes[] = {
// type : username : password @ hostname (/ name)?(:port)?
".*imaps?:([^: ]{1,256}):([^@]{0,32})@([A-Za-z1-9][-A-Za-z0-9_.]+)(/(\"[^\"]+\")|([^:@ ]+))?(:[0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
".*imaps?:([^: ]{1,256}) ([^ ]{1,32}) ([A-Za-z1-9][-A-Za-z0-9_.]+)(/(\"[^\"]+\")|([^: ]+))?( [0-9]+)?( *([CcAaPp][-A-Za-z5 ]*))?$",
NULL
};
char *unaliased_str;
struct regulo regulos[] = {
{1, PCU.userName, regulo_strcpy},
{2, PCU.password, regulo_strcpy},
{3, PCU.serverName, regulo_strcpy},
{4, pc->path, regulo_strcpy_skip1},
{7, &PCU.serverPort, regulo_atoi},
{9, PCU.authList, regulo_strcpy_tolower},
{0, NULL, NULL}
};
if (Relax) {
regexes[0] =
".*imaps?:([^: ]{1,256}):([^@]{0,32})@([^/: ]+)(/(\"[^\"]+\")|([^:@ ]+))?(:[0-9]+)?( *(.*))?$";
regexes[1] =
".*imaps?:([^: ]{1,256}) ([^ ]{1,32}) ([^/: ]+)(/(\"[^\"]+\")|([^: ]+))?( [0-9]+)?( *(.*))?$";
}
/* IMAP4 format: imap:user:password@server/mailbox[:port] */
/* If 'str' line is badly formatted, wmbiff won't display the mailbox. */
if (strncmp("sslimap:", str, 8) == 0 || strncmp("imaps:", str, 6) == 0) {
#ifdef HAVE_GNUTLS_GNUTLS_H
PCU.dossl = 1;
#else
printf("This copy of wmbiff was not compiled with gnutls;\n"
"imaps is unavailable. Exiting to protect your\n"
"passwords and privacy.\n");
exit(EXIT_FAILURE);
#endif
} else {
PCU.dossl = 0;
}
/* defaults */
PCU.serverPort = (PCU.dossl != 0) ? 993 : 143;
PCU.authList[0] = '\0';
/* argh, str and pc->path are aliases, so we can't just write the default
value into the string we're about to parse. */
unaliased_str = strdup(str);
strcpy(pc->path, "INBOX");
for (matchedchars = 0, i = 0;
regexes[i] != NULL && matchedchars <= 0; i++) {
matchedchars = regulo_match(regexes[i], unaliased_str, regulos);
}
/* failed to match either regex */
if (matchedchars <= 0) {
pc->label[0] = '\0';
IMAP_DM(pc, DEBUG_ERROR, "Couldn't parse line %s (%d)\n"
" If this used to work, run wmbiff with the -relax option, and\n"
" send mail to "PACKAGE_BUGREPORT" with the hostname\n"
" of your mail server.\n", unaliased_str, matchedchars);
return -1;
}
PCU.password_len = strlen(PCU.password);
if (PCU.password[0] == '\0') {
PCU.interactive_password = 1;
} else {
ENFROB(PCU.password);
}
// grab_authList(unaliased_str + matchedchars, PCU.authList);
free(unaliased_str);
IMAP_DM(pc, DEBUG_INFO, "userName= '%s'\n", PCU.userName);
IMAP_DM(pc, DEBUG_INFO, "password is %d characters long\n",
(int) PCU.password_len);
IMAP_DM(pc, DEBUG_INFO, "serverName= '%s'\n", PCU.serverName);
IMAP_DM(pc, DEBUG_INFO, "serverPath= '%s'\n", pc->path);
IMAP_DM(pc, DEBUG_INFO, "serverPort= '%d'\n", PCU.serverPort);
IMAP_DM(pc, DEBUG_INFO, "authList= '%s'\n", PCU.authList);
if (strcmp(pc->action, "msglst") == 0 ||
strcmp(pc->fetchcmd, "msglst") == 0 ||
strcmp(pc->button2, "msglst") == 0) {
PCU.wantCacheHeaders = 1;
} else {
PCU.wantCacheHeaders = 0;
}
pc->checkMail = imap_checkmail;
pc->getHeaders = imap_getHeaders;
pc->releaseHeaders = imap_releaseHeaders;
pc->TotalMsgs = 0;
pc->UnreadMsgs = 0;
pc->OldMsgs = -1;
pc->OldUnreadMsgs = -1;
return 0;
}
static int authenticate_plaintext( /*@notnull@ */ Pop3 pc,
struct connection_state *scs,
const char *capabilities)
{
char buf[BUF_SIZE];
/* is login prohibited? */
/* "An IMAP client which complies with [rfc2525, section 3.2]
* MUST NOT issue the LOGIN command if this capability is present.
*/
if (strstr(capabilities, "LOGINDISABLED")) {
IMAP_DM(pc, DEBUG_ERROR,
"Plaintext auth prohibited by server: (LOGINDISABLED).\n");
goto plaintext_failed;
}
ask_user_for_password(pc, 0);
do {
/* login */
DEFROB(PCU.password);
tlscomm_printf(scs, "a001 LOGIN %s \"%s\"\r\n", PCU.userName,
PCU.password);
ENFROB(PCU.password);
if (tlscomm_expect(scs, "a001 ", buf, BUF_SIZE) == 0) {
IMAP_DM(pc, DEBUG_ERROR,
"Did not get a response to the LOGIN command.\n");
goto plaintext_failed;
}
if (buf[5] != 'O') {
IMAP_DM(pc, DEBUG_ERROR, "IMAP Login failed: %s\n", buf);
/* if we're prompting the user, ask again, else fail */
if (PCU.interactive_password) {
PCU.password[0] = '\0';
ask_user_for_password(pc, 1); /* 1=overwrite the cache */
} else {
goto plaintext_failed;
}
} else {
return (1);
}
}
while (1);
plaintext_failed:
return (0);
}
#ifdef HAVE_GCRYPT_H
static int
authenticate_md5(Pop3 pc,
struct connection_state *scs, const char *capabilities)
{
char buf[BUF_SIZE];
char buf2[BUF_SIZE];
unsigned char *md5;
gcry_md_hd_t gmh;
gcry_error_t rc;
if (!strstr(capabilities, "AUTH=CRAM-MD5")) {
/* server doesn't support cram-md5. */
return 0;
}
tlscomm_printf(scs, "a007 AUTHENTICATE CRAM-MD5\r\n");
if (tlscomm_expect(scs, "+ ", buf, BUF_SIZE) == 0)
goto expect_failure;
Decode_Base64(buf + 2, buf2);
IMAP_DM(pc, DEBUG_INFO, "CRAM-MD5 challenge: %s\n", buf2);
strcpy(buf, PCU.userName);
strcat(buf, " ");
ask_user_for_password(pc, 0);
rc = gcry_md_open(&gmh, GCRY_MD_MD5, GCRY_MD_FLAG_HMAC);
if (rc != 0) {
IMAP_DM(pc, DEBUG_INFO, "unable to initialize gcrypt md5\n");
return 0;
}
DEFROB(PCU.password);
gcry_md_setkey(gmh, PCU.password, strlen(PCU.password));
ENFROB(PCU.password);
gcry_md_write(gmh, (unsigned char *) buf2, strlen(buf2));
gcry_md_final(gmh);
md5 = gcry_md_read(gmh, 0);
Bin2Hex(md5, 16, buf2);
gcry_md_close(gmh);
strcat(buf, buf2);
IMAP_DM(pc, DEBUG_INFO, "CRAM-MD5 response: %s\n", buf);
Encode_Base64(buf, buf2);
tlscomm_printf(scs, "%s\r\n", buf2);
if (tlscomm_expect(scs, "a007 ", buf, BUF_SIZE) == 0)
goto expect_failure;
if (!strncmp(buf, "a007 OK", 7))
return 1; /* AUTH successful */
IMAP_DM(pc, DEBUG_ERROR,
"CRAM-MD5 AUTH failed for user '%s@%s:%d'\n",
PCU.userName, PCU.serverName, PCU.serverPort);
IMAP_DM(pc, DEBUG_INFO, "It said %s", buf);
return 0;
expect_failure:
IMAP_DM(pc, DEBUG_ERROR,
"tlscomm_expect failed during cram-md5 auth: %s", buf);
IMAP_DM(pc, DEBUG_ERROR, "failed to authenticate using cram-md5.");
return 0;
}
#endif
static void ask_user_for_password( /*@notnull@ */ Pop3 pc, int bFlushCache)
{
/* see if we already have a password, as provided in the config file, or
already requested from the user. */
if (PCU.interactive_password) {
if (strlen(PCU.password) == 0) {
/* we need to grab the password from the user. */
char *password;
IMAP_DM(pc, DEBUG_INFO, "asking for password %d\n",
bFlushCache);
password =
passwordFor(PCU.userName, PCU.serverName, pc, bFlushCache);
if (password != NULL) {
if (strlen(password) + 1 > BUF_SMALL) {
DMA(DEBUG_ERROR, "Password is too long.\n");
memset(PCU.password, 0, BUF_SMALL - 1);
} else {
strncpy(PCU.password, password, BUF_SMALL - 1);
PCU.password_len = strlen(PCU.password);
}
free(password);
ENFROB(PCU.password);
}
}
}
}
/* vim:set ts=4: */
/*
* Local Variables:
* tab-width: 4
* c-indent-level: 4
* c-basic-offset: 4
* End:
*/