diff -ur qpopper4.0.5-dist/configure.in qpopper4.0.5-happymail/configure.in --- qpopper4.0.5-dist/configure.in Tue Feb 4 21:06:08 2003 +++ qpopper4.0.5-happymail/configure.in Fri May 23 01:02:05 2003 @@ -878,6 +884,14 @@ fi else AC_DEFINE_UNQUOTED( CHUNKY_WRITES, 0 ) +fi + + +AC_ARG_ENABLE(happymail, [ --enable-happymail Enable per-user minimum mail check limits ], + use_happymail="$enableval", use_happymail="no") +if test "$use_happymail" != "no"; then + AC_MSG_RESULT(Enabling HAPPYMAIL mail check time limits) + OS_DEFS="$OS_DEFS -DHAPPYMAIL" fi diff -ur qpopper4.0.5-dist/popper/pop_config.c qpopper4.0.5-happymail/popper/pop_config.c --- qpopper4.0.5-dist/popper/pop_config.c Wed Mar 12 20:06:37 2003 +++ qpopper4.0.5-happymail/popper/pop_config.c Fri May 23 09:18:49 2003 @@ -229,6 +229,14 @@ kLOG_FACILITY, /* -y facility */ kLOG_LOGIN, /* (no flag) */ kMAXBULLS, /* (no flag) */ +#ifdef HAPPYMAIL + kHM_BASE, /* -h BASE+secs:bytes@free/max */ + kHM_RATE_S, /* -h base+SECS:bytes@free/max */ + kHM_RATE_B, /* -h base+secs:BYTES@free/max */ + kHM_FREE, /* -h base+secs:bytes@FREE/max */ + kHM_MAX, /* -h base+secs:bytes@free/MAX */ + kHM_BUFSIZ, /* -z bufsiz */ +#endif /* HAPPYMAIL */ LAST_OPT_VAL @@ -263,6 +271,14 @@ { "group-bulletins" , CfgBool , CfgResUser, kGROUP_BULLS }, { "group-no-server-mode" , CfgStr , CfgResUser, kGRP_NO_SERV_MODE }, { "group-server-mode" , CfgStr , CfgResUser, kGRP_SERV_MODE }, +#ifdef HAPPYMAIL + { "happymail-base" , CfgIntTime , CfgResInit, kHM_BASE }, + { "happymail-rate-seconds" , CfgIntTime , CfgResInit, kHM_RATE_S }, + { "happymail-rate-bytes" , CfgIntSpace , CfgResInit, kHM_RATE_B }, + { "happymail-free" , CfgIntSpace , CfgResInit, kHM_FREE }, + { "happymail-max" , CfgIntTime , CfgResInit, kHM_MAX }, + { "happymail-bufsiz" , CfgIntSpace , CfgResInit, kHM_BUFSIZ }, +#endif /* HAPPYMAIL */ { "hash-spool" , CfgInt , CfgResUser, kHASH_SPOOL }, { "home-dir-mail" , CfgStr , CfgResUser, kHOME_DIR_MAIL }, { "keep-temp-drop" , CfgBool , CfgResUser, kKEEP_TEMP_DROP }, @@ -908,6 +924,14 @@ case kLOG_FACILITY: R__MNM ( &p->log_facility ); case kLOG_LOGIN: R__PTR ( &p->pLog_login ); case kMAXBULLS: R__INT ( &p->nMaxBulls ); +#ifdef HAPPYMAIL + case kHM_BASE: R__INT ( &p->happymail_base ); + case kHM_RATE_S: R__INT ( &p->happymail_rate_s ); + case kHM_RATE_B: R__INT ( &p->happymail_rate_b ); + case kHM_FREE: R__INT ( &p->happymail_free ); + case kHM_MAX: R__INT ( &p->happymail_max ); + case kHM_BUFSIZ: R__INT ( &p->happymail_bufsiz ); +#endif /* HAPPYMAIL */ default: R__PTR ( NULL ); } /* switch ( item ) */ @@ -1034,6 +1058,20 @@ opt->name, sVal ); break; +#ifdef HAPPYMAIL + case CfgIntTime: + if ( p->debug ) + pop_log ( p, POP_INFO, fn, ln, "Set %s to %s (%d)", + opt->name, sVal, iVal ); + break; + + case CfgIntSpace: + if ( p->debug ) + pop_log ( p, POP_INFO, fn, ln, "Set %s to %s (%d)", + opt->name, sVal, iVal ); + break; +#endif /* HAPPYMAIL */ + default: /* we should never get here */ pop_log ( p, POP_PRIORITY, fn, ln, @@ -1249,6 +1287,28 @@ } show_result ( p, opt_ent, 0, *pOpt.pPtr, HERE ); break; + +#ifdef HAPPYMAIL + /* Handle integer types with space/time units, per happymail */ + + case CfgIntTime: + pOpt = get_opt_ptr ( p, opt_ent->value ); + if (pOpt.pInt == NULL) + return kER_INTERNAL; + + *pOpt.pInt = happyunits_time(pval); + show_result ( p, opt_ent, *pOpt.pInt, pval, HERE ); + break; + + case CfgIntSpace: + pOpt = get_opt_ptr ( p, opt_ent->value ); + if (pOpt.pInt == NULL) + return kER_INTERNAL; + + *pOpt.pInt = happyunits_space(pval); + show_result ( p, opt_ent, *pOpt.pInt, pval, HERE ); + break; +#endif /* HAPPYMAIL */ default: /* we should never get here */ diff -ur qpopper4.0.5-dist/popper/pop_dropcopy.c qpopper4.0.5-happymail/popper/pop_dropcopy.c --- qpopper4.0.5-dist/popper/pop_dropcopy.c Wed Jan 1 20:39:02 2003 +++ qpopper4.0.5-happymail/popper/pop_dropcopy.c Fri May 23 10:16:12 2003 @@ -800,7 +800,11 @@ /* * Acquire a stream pointer for the maildrop */ +#ifdef HAPPYMAIL + mail_drop = fdopen_happy ( p, mfd, "r" ); +#else mail_drop = fdopen ( mfd, "r" ); +#endif /* HAPPYMAIL */ if ( mail_drop == NULL ) { return pop_msg ( p, POP_FAILURE, HERE, "[SYS/TEMP] Cannot assign stream for %s: %s (%d)", @@ -1205,13 +1209,63 @@ time_t my_timer = 0; +#ifndef HAPPYMAIL /* we need to do this always for happymail */ if ( p->bDo_timing ) +#endif /* HAPPYMAIL */ my_timer = time(0); if ( genpath ( p, p->drop_name, sizeof(p->drop_name), GNPH_SPOOL ) < 0 ) return pop_msg ( p, POP_FAILURE, HERE, "[SYS/TEMP] Unable to get spool name" ); +#ifdef HAPPYMAIL + if (pwp->pw_uid >= 0 && pwp->pw_uid < 64*1024 && + (p->happymail_base || p->happymail_free >= 0)) { + + int mydelay, adjust = 0; + + if (p->happymail_rate_s) { + if (stat(p->drop_name, &mybuf) == 0) { + if (mybuf.st_size <= p->happymail_free) + adjust = 0; + else + adjust = (p->happymail_rate_s * + ((mybuf.st_size - p->happymail_free) + / p->happymail_rate_b)); + } + } + + mydelay = (p->happymail_base + adjust); + mydelay -= mydelay % 60; /* should be a round # of minutes */ + + + /* don't allow mydelay to be larger than max */ + if (p->happymail_max > 0 && mydelay > p->happymail_max) + mydelay = p->happymail_max; + + if (my_timer - p->happymail_tab[pwp->pw_uid] < mydelay - 30) { + return (pop_msg(p, POP_FAILURE, HERE, + "[HAPPYMAIL] Please wait at least %d minutes " + "between checks. " + "[%d+%d:%d@%d!%d (%d) => %d + %d %s %d] ", + mydelay/60, + p->happymail_base, + p->happymail_rate_s, + p->happymail_rate_b, + p->happymail_free, + p->happymail_max, + mybuf.st_size, + p->happymail_base, + adjust, + p->happymail_base + adjust > p->happymail_max ? + ">" : "=>", + mydelay + )); + } + p->happymail_tab[pwp->pw_uid] = my_timer; + } +#endif /* HAPPYMAIL */ + if ( ( p->hash_spool > 0 || p->pHome_dir_mail != NULL ) && p->bCheck_old_spool_loc ) { /* @@ -1573,7 +1627,11 @@ /* * Acquire a stream pointer for the temporary maildrop */ +#ifdef HAPPYMAIL + p->drop = fdopen_happy ( p, dfd, "r+" ); +#else p->drop = fdopen ( dfd, "r+" ); +#endif /* HAPPYMAIL */ if ( p->drop == NULL ) { flock ( dfd, LOCK_UN ); close ( dfd ); @@ -1701,7 +1759,11 @@ * Save the temporary drop FILE and fid values */ p->hold = p->drop; +#ifdef HAPPYMAIL + p->drop = fdopen_happy ( p, mfd, "r+" ); +#else p->drop = fdopen ( mfd, "r+" ); +#endif /* HAPPYMAIL */ if ( p->drop == NULL ) { pop_msg ( p, POP_FAILURE, HERE, "[SYS/TEMP] Cannot assign stream for %s: %s (%d)", diff -ur qpopper4.0.5-dist/popper/pop_extend.c qpopper4.0.5-happymail/popper/pop_extend.c --- qpopper4.0.5-dist/popper/pop_extend.c Wed Mar 12 20:06:37 2003 +++ qpopper4.0.5-happymail/popper/pop_extend.c Fri May 23 11:01:20 2003 @@ -107,6 +107,16 @@ POP_WRITE_LIT ( p, "X-LOCALTIME " ); pop_write_line ( p, get_time() ); +#ifdef HAPPYMAIL + pop_write_fmt ( p, "X-HAPPYMAIL " HAPPYMAIL_PATCHLEVEL " bufsiz:%d base:%d rate:%ds/%db free:%db max:%ds\r\n", + p->happymail_bufsiz, + p->happymail_base, + p->happymail_rate_s, + p->happymail_rate_b, + p->happymail_free, + p->happymail_max); +#endif /* HAPPYMAIL */ + if ( p->tls_support == QPOP_TLS_STLS ) POP_WRITE_LIT ( p, "STLS\r\n" ); diff -ur qpopper4.0.5-dist/popper/pop_init.c qpopper4.0.5-happymail/popper/pop_init.c --- qpopper4.0.5-dist/popper/pop_init.c Wed Mar 12 20:06:37 2003 +++ qpopper4.0.5-happymail/popper/pop_init.c Fri May 23 10:47:17 2003 @@ -171,6 +171,14 @@ extern int h_errno; /* Error external for gethostxxxx library */ #endif + +#ifdef HAPPYMAIL +# include +# include +# include +#endif /* HAPPYMAIL */ + + /* Method to get friendly string from h_errno */ #ifndef HAVE_HSTRERROR const char * _HERRORS[] = @@ -428,6 +446,53 @@ return ( POP_SUCCESS ); } + +#ifdef HAPPYMAIL +/* + * Unit conversions for happymail parameters + */ +int +happyunits_space(char *s) +{ + char *unit; + int n; + + n = strtol(s, &unit, 0); + if (unit == s) /* if no number given, only unit, infer 1. */ + n = 1; + if (unit && *unit == 'b') + n *= 1; + else if (unit && *unit == 'k') + n *= 1024; + else if (unit && *unit == 'm') + n *= 1024 * 1024; + else if (unit && *unit == 'g') + n *= 1024 * 1024 * 1024; + return n; +} + +int +happyunits_time(char *s) +{ + char *unit; + int n; + + n = strtol(s, &unit, 0); + if (unit == s) /* if no number given, only unit, infer 1. */ + n = 1; + if (unit && *unit == 's') + n *= 1; + else if (unit && *unit == 'm') + n *= 60; + else if (unit && *unit == 'h') + n *= 60 * 60; + else if (unit && *unit == 'd') + n *= 60 * 60 * 24; + return n; +} +#endif /* HAPPYMAIL */ + + /* * init: Start a Post Office Protocol session */ @@ -689,10 +754,25 @@ openlog ( p->myname, POP_LOGOPTS, p->log_facility ); #endif +#ifdef HAPPYMAIL + /* minimum mail check timer value, seconds */ + p->happymail_base = 0; + /* rate at which we add to timer -- rate_s seconds per rate_b bytes */ + p->happymail_rate_s = 0; + p->happymail_rate_b = 1; /* should be non-zero */ + /* how much mail is "free" -- less does not invoke rate adjustment */ + p->happymail_free = -1; /* all free */ + /* maximum timer value, no matter how much mail they have */ + p->happymail_max = 0; /* no maximum */ + /* bulk copy blocksize */ + p->happymail_bufsiz = 0; /* use the default, don't setvbuf() */ +#endif /* HAPPYMAIL */ + + /* * Process command line arguments */ - while ( ( c = getopt ( argcount, argmessage, "b:BcCdD:e:f:FkK:l:L:p:RsSt:T:uUvy:") ) != EOF ) + while ( ( c = getopt ( argcount, argmessage, "b:BcCdD:e:f:Fh:kK:l:L:p:RsSt:T:uUvy:z:") ) != EOF ) switch ( c ) { case 'b': /* Bulletins requested */ @@ -762,6 +842,41 @@ DEBUG_LOG0 ( p, "set fast-update" ); break; +#ifdef HAPPYMAIL + case 'h': /* happymail timeout extravaganza */ + { + char *s1, *s2; + int n; + + if (s1 = strchr(optarg, '+')) { + /* rate information starts here */ + if (s1 != optarg) + p->happymail_base = happyunits_time(optarg); + ++s1; + if (s2 = strchr(s1, '!')) { + /* we also have a max value */ + p->happymail_max = happyunits_space(++s2); + } + if (s2 = strchr(s1, '@')) { + /* we also have a free value */ + p->happymail_free = happyunits_space(++s2); + } + if (s2 = strchr(s1, ':')) { + /* rate delimiter */ + p->happymail_rate_b = happyunits_space(++s2); + } + p->happymail_rate_s = happyunits_time(s1); + } else { + /* we have only a global timer */ + p->happymail_base = happyunits_time(optarg); + } + } + + if (p->happymail_rate_b == 0) + p->happymail_rate_b = 1; /* it's a divisor; must be nonzero */ + +#endif /* HAPPYMAIL */ + #ifdef KERBEROS case 'k': p->bKerberos = TRUE; @@ -920,6 +1035,12 @@ errflag++; } +#ifdef HAPPYMAIL + case 'z': /* buffer size */ + p->happymail_bufsiz = happyunits_space(optarg); + break; +#endif /* HAPPYMAIL */ + default: /* Unknown option received */ errflag++; } @@ -967,6 +1088,14 @@ char *xl = ""; #endif /* QPOP_SSL */ +#ifdef HAPPYMAIL + char *xh = "[-h [basetimer][+seconds:bytes[@basesize][!maxtimer]] "; + char *xz = "[-z bufsize] "; +#else + char *xh = ""; + char *xz = ""; +#endif /* HAPPYMAIL */ + fprintf ( stderr, "Usage: %s " "[-b bulldir] " @@ -977,6 +1106,7 @@ "[-e login_delay=nn,expire=nn] " "[-f config-file] " "[-F] " + "%s" /* -h / happymail mincheck */ "%s" /* -D / drac-host, if DRAC set */ "%s" /* -k / kerberos, if KERBEROS set */ "%s" /* -K / kerberos service, if KERBEROS set */ @@ -991,17 +1121,35 @@ "[-u] " "[-U] " "[-v] " + "%s" /* -z / happymail bufsize */ "\n", argmessage[0], xB, /* -B (or not) */ + xh, /* -h (or not) */ xD, /* -D (or not) */ xk, /* -k (or not) */ xK, /* -K (or not) */ xl, /* -l (or not) */ - xp ); /* -p (or not) */ + xp, /* -p (or not) */ + xz ); /* -z (or not) */ EXIT ( 1 ); } +#ifdef HAPPYMAIL + /* If bufsiz < 1k, assume it's measured in kilobytes and multiply. */ + if (p->happymail_bufsiz && p->happymail_bufsiz < 1024) + p->happymail_bufsiz *= 1024; + + DEBUG_LOG6 ( p, + "happymail buf=%db, base=%ds, rate=%ds/%db, free=%db, max=%db", + p->happymail_bufsiz, + p->happymail_base, + p->happymail_rate_s, + p->happymail_rate_b, + p->happymail_free, + p->happymail_max); +#endif /* HAPPYMAIL */ + #ifndef QPOP_SSL if ( p->tls_support != QPOP_TLS_NONE ) { /* This the main ifdef for SSL in the bulk of qpopper. Most of the @@ -1181,6 +1329,51 @@ STRERROR(errno), errno); EXIT ( 1 ); } + + +#ifdef HAPPYMAIL + { + int shmid, key, i, isnew = 0; + + key = 'P' << 24 | 'O' << 16 | 'P' << 8 | '3'; + shmid = shmget(key, sizeof(time_t) * 64 * 1024, 0600); + if (shmid < 0) { + /* assume it doesn't yet exist */ + shmid = shmget(key, sizeof(time_t) * 64 * 1024, IPC_CREAT | 0600); + if (shmid) + isnew = 1; + } + if (shmid < 0) { + pop_log ( p, POP_PRIORITY, HERE, + "Unable to get happymail shmseg 0x%08x", key); + p->happymail_base = 0; /* allow all */ + p->happymail_free = -1; /* all mail is free */ + } else { + p->happymail_tab = shmat(shmid, NULL, 0); + if (p->happymail_tab == (void *)-1) { + pop_log ( p, POP_PRIORITY, HERE, + "Unable to attach happymail shmseg 0x%08x", + key); + p->happymail_base = 0; /* allow all */ + p->happymail_free = -1; /* all mail is free */ + } else { + if (isnew) { + struct shmid_ds shmattrs; + + /* set all times to origin */ + for (i = 0; i < 64 * 1024; ++i) { + p->happymail_tab[i] = (time_t)0; + } + + /* set permissions on segment to rw-rw---- */ + shmctl(shmid, IPC_STAT, &shmattrs); + shmattrs.shm_perm.mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP; + shmctl(shmid, IPC_SET, &shmattrs); + } + } + } + } +#endif DEBUG_LOG3 ( p, "(v%s) Servicing request from \"%s\" at %s", VERSION, p->client, p->ipaddr ); diff -ur qpopper4.0.5-dist/popper/pop_pass.c qpopper4.0.5-happymail/popper/pop_pass.c --- qpopper4.0.5-dist/popper/pop_pass.c Wed Mar 12 20:06:37 2003 +++ qpopper4.0.5-happymail/popper/pop_pass.c Mon Mar 17 11:34:46 2003 @@ -1387,6 +1387,19 @@ return ( POP_FAILURE ); } +#ifdef HAPPYMAIL_OLD + if (pwp->pw_uid >= 0 && pwp->pw_uid < 64*1024 && + (p->happymail_base || p->happymail_free >= 0)) { + if (my_timer - p->happymail_tab[pwp->pw_uid] < + (p->happymail_base * 60) - 30) { + return ( pop_msg ( p, POP_FAILURE, HERE, + "Please wait at least %d minutes between checks", + p->happymail_base)); + } + p->happymail_tab[pwp->pw_uid] = my_timer; + } +#endif /* HAPPYMAIL */ + #ifdef SECURENISPLUS seteuid ( uid_save ); #endif /* SECURENISPLUS */ diff -ur qpopper4.0.5-dist/popper/pop_updt.c qpopper4.0.5-happymail/popper/pop_updt.c --- qpopper4.0.5-dist/popper/pop_updt.c Wed Jan 1 20:39:03 2003 +++ qpopper4.0.5-happymail/popper/pop_updt.c Mon Mar 17 11:34:46 2003 @@ -366,7 +366,11 @@ * Open the user's real maildrop */ if ( ( mfd = open ( p->drop_name, O_RDWR | O_CREAT, 0660 ) ) == -1 || +#ifdef HAPPYMAIL + ( md = fdopen_happy ( p, mfd, "r+" ) ) == NULL ) { +#else ( md = fdopen ( mfd, "r+" ) ) == NULL ) { +#endif /* HAPPYMAIL */ Qmailunlock ( HERE ); return pop_msg ( p, POP_FAILURE, HERE, standard_error, STRERROR(errno), errno ); @@ -747,7 +751,11 @@ mfd = open ( p->temp_drop, O_RDWR | O_CREAT, 0600 ); if ( mfd >= 0 ) { DEBUG_LOG1 ( p, "Reopened mfd as temp drop (%d)", mfd ); +#ifdef HAPPYMAIL + md = fdopen_happy ( p, mfd, "r+" ); +#else md = fdopen ( mfd, "r+" ); +#endif /* HAPPYMAIL */ if ( md == NULL ) pop_log ( p, POP_NOTICE, HERE, "Unable to reopen md from mfd (%d): %s (%d)", @@ -940,7 +948,11 @@ STRERROR(errno), errno ) ); if ( ( mfd = open ( p->drop_name, O_RDWR | O_CREAT, 0660 ) ) == -1 || +#ifdef HAPPYMAIL + ( md = fdopen_happy ( p, mfd, "r+" ) ) == NULL ) { +#else ( md = fdopen ( mfd, "r+" ) ) == NULL ) { +#endif /* HAPPYMAIL */ Qmailunlock ( HERE ); return pop_msg ( p, POP_FAILURE, HERE, standard_error, STRERROR ( errno ), errno ); diff -ur qpopper4.0.5-dist/popper/popper.c qpopper4.0.5-happymail/popper/popper.c --- qpopper4.0.5-dist/popper/popper.c Wed Mar 12 20:06:38 2003 +++ qpopper4.0.5-happymail/popper/popper.c Mon Mar 17 11:34:46 2003 @@ -837,6 +837,12 @@ p->user ); } +#ifdef HAPPYMAIL + if ( p->happymail_tab ) { + shmdt((void *)p->happymail_tab); + } +#endif /* HAPPYMAIL */ + if ( p->pw.pw_dir != NULL ) { free ( p->pw.pw_dir ); } @@ -884,3 +890,32 @@ return buf; } +#ifdef HAPPYMAIL +FILE * +fopen_happy( POP *p, char *path, char *mode ) +{ + FILE *fp; + + if ( ! (fp = fopen(path, mode)) ) + return NULL; + + if ( p->happymail_bufsiz ) + setvbuf( fp, NULL, _IOFBF, p->happymail_bufsiz ); + + return fp; +} + +FILE * +fdopen_happy( POP *p, int fildes, char *mode ) +{ + FILE *fp; + + if ( ! (fp = fdopen(fildes, mode)) ) + return NULL; + + if ( p->happymail_bufsiz ) + setvbuf( fp, NULL, _IOFBF, p->happymail_bufsiz ); + + return fp; +} +#endif /* HAPPYMAIL */ diff -ur qpopper4.0.5-dist/popper/popper.h qpopper4.0.5-happymail/popper/popper.h --- qpopper4.0.5-dist/popper/popper.h Wed Mar 12 20:06:38 2003 +++ qpopper4.0.5-happymail/popper/popper.h Fri May 23 09:39:49 2003 @@ -101,6 +101,10 @@ #define ALLOC_MSGS 20 #define OUT_BUF_SIZE 512 /* Amount of output to buffer before forcing a write */ +#ifdef HAPPYMAIL +# define HAPPYMAIL_PATCHLEVEL "pl8" +#endif /* HAPPYMAIL */ + #ifndef MAXHOSTNAMELEN # define MAXHOSTNAMELEN 64 #endif /* not MAXHOSTNAMELEN */ @@ -418,6 +422,10 @@ CfgInt, /* - integer */ CfgMnem, /* - mnemonic */ CfgStr, /* - string */ +#ifdef HAPPYMAIL + CfgIntTime, /* - int, with time units */ + CfgIntSpace, /* - int, with space units */ +#endif /* HAPPYMAIL */ CfgBad /* - INVALID VALUE */ } config_opt_type; @@ -648,6 +656,16 @@ char * drac_host; /* Host which handles drac */ #endif /* DRAC_AUTH */ +#ifdef HAPPYMAIL + int happymail_base; /* min check limit */ + int happymail_rate_s; /* how many seconds to add... */ + int happymail_rate_b; /* ...per how many mb */ + int happymail_free; /* how much mail is free */ + int happymail_max; /* upper boundary on timer */ + time_t * happymail_tab; /* per-uid shmem check table */ + int happymail_bufsiz; /* buffer size for maildrops */ +#endif /* HAPPYMAIL */ + BOOL bUser_opts; /* Process ~/.qpopper-options */ BOOL bSpool_opts; /* Process .qpopper-options */ BOOL bFast_update; /* Use rename(2) instead of copying */ @@ -806,6 +824,10 @@ int checkauthfile (POP *p); int checknonauthfile (POP *p); void do_log_login (POP *p); +#ifdef HAPPYMAIL +FILE *fopen_happy (POP *p, char *path, char *mode); +FILE *fdopen_happy (POP *p, int fildes, char *mode); +#endif /* HAPPYMAIL */ int qpopper ( int argc, char *argv[] );