#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
#include <syslog.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/stat.h>
#ifdef APPLE_OS_X_SERVER
#include <com_err.h>
#include <pwd.h>
#include <sys/wait.h>
#endif
#if HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# if HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif
# if HAVE_NDIR_H
# include <ndir.h>
# endif
#endif
#include "assert.h"
#include "cyrusdb.h"
#include "global.h"
#include "exitcodes.h"
#include "imap_err.h"
#include "mailbox.h"
#include "xmalloc.h"
#include "xstrlcpy.h"
#include "xstrlcat.h"
#include "mboxlist.h"
#include "mboxname.h"
#include "quota.h"
#include "convert_code.h"
#include "util.h"
extern int optind;
extern char *optarg;
static struct namespace quota_namespace;
const int config_need_data = CONFIG_NEED_PARTITION_DATA;
struct fix_rock {
char *domain;
struct txn **tid;
unsigned long change_count;
};
struct quotaentry {
#ifdef APPLE_OS_X_SERVER
struct quota mail_quota;
#else
struct quota quota;
#endif
int refcount;
int deleted;
uquota_t newused;
};
void usage(void);
void reportquota(void);
int buildquotalist(char *domain, char **roots, int nroots,
struct fix_rock *frock);
int fixquota_mailbox(char *name, int matchlen, int maycreate, void *rock);
int fixquota(struct fix_rock *frock);
int fixquota_fixroot(struct mailbox *mailbox, char *root);
int fixquota_finish(int thisquota, struct txn **tid, unsigned long *count);
#ifdef APPLE_OS_X_SERVER
void genquotareport(void);
void doquotacheck( void );
#endif
#define QUOTAGROW 300
#ifdef APPLE_OS_X_SERVER
struct quotaentry *quota_list;
#else
struct quotaentry *quota;
#endif
int quota_num = 0, quota_alloc = 0;
int firstquota = 0;
int redofix = 0;
int partial;
int main(int argc,char **argv)
{
int opt;
int fflag = 0;
int r, code = 0;
char *alt_config = NULL, *domain = NULL;
struct fix_rock frock;
struct txn *tid = NULL;
#ifdef APPLE_OS_X_SERVER
int qflag = 0;
int rflag = 0;
#endif
if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
#ifdef APPLE_OS_X_SERVER
while ((opt = getopt(argc, argv, "C:d:fqr")) != EOF) {
#else
while ((opt = getopt(argc, argv, "C:d:f")) != EOF) {
#endif
switch (opt) {
case 'C':
alt_config = optarg;
break;
case 'd':
domain = optarg;
break;
case 'f':
fflag = 1;
break;
#ifdef APPLE_OS_X_SERVER
case 'q':
qflag = 1;
fflag = 1;
break;
case 'r':
rflag = 1;
break;
#endif
default:
usage();
}
}
#ifdef APPLE_OS_X_SERVER
cyrus_init(alt_config, "cyrus-quota", 0);
#else
cyrus_init(alt_config, "quota", 0);
#endif
if ((r = mboxname_init_namespace("a_namespace, 1)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
mboxlist_init(0);
mboxlist_open(NULL);
quotadb_init(0);
quotadb_open(NULL);
if (!r) {
frock.domain = domain;
frock.tid = &tid;
frock.change_count = 0;
r = buildquotalist(domain, argv+optind, argc-optind,
fflag ? &frock : NULL);
}
if (!r && fflag) {
partial = argc-optind;
r = fixquota(&frock);
}
quotadb_close();
quotadb_done();
mboxlist_close();
mboxlist_done();
#ifdef APPLE_OS_X_SERVER
if(!r)
{
if ( qflag == 1 )
{
doquotacheck();
}
else if ( rflag == 1 )
{
genquotareport();
}
else
{
reportquota();
}
}
else
{
code = convert_code(r);
}
#else
if (r) code = convert_code(r);
else reportquota();
#endif
cyrus_done();
return code;
}
void usage(void)
{
fprintf(stderr,
#ifdef APPLE_OS_X_SERVER
"usage: cyrus-quota [-C <alt_config>] [-d <domain>] [-f] [prefix]...\n");
#else
"usage: quota [-C <alt_config>] [-d <domain>] [-f] [prefix]...\n");
#endif
exit(EC_USAGE);
}
void errmsg(const char *fmt, const char *arg, int err)
{
char buf[1024];
int len;
len = snprintf(buf, sizeof(buf), fmt, arg);
if (len < sizeof(buf))
len += snprintf(buf+len, sizeof(buf)-len, ": %s", error_message(err));
if ((err == IMAP_IOERROR) && (len < sizeof(buf)))
len += snprintf(buf+len, sizeof(buf)-len, ": %%m");
syslog(LOG_ERR, buf);
fprintf(stderr, "%s\n", buf);
}
static int found_match(char *name, int matchlen, int maycreate, void *frock)
{
int r;
if (quota_num == quota_alloc) {
quota_alloc += QUOTAGROW;
#ifdef APPLE_OS_X_SERVER
quota_list = (struct quotaentry *)
xrealloc((char *)quota_list, quota_alloc * sizeof(struct quotaentry));
memset("a_list[quota_num], 0, QUOTAGROW * sizeof(struct quotaentry));
#else
quota = (struct quotaentry *)
xrealloc((char *)quota, quota_alloc * sizeof(struct quotaentry));
memset("a[quota_num], 0, QUOTAGROW * sizeof(struct quotaentry));
#endif
}
#ifdef APPLE_OS_X_SERVER
quota_list[quota_num].mail_quota.root = name;
do {
r = quota_read("a_list[quota_num].mail_quota, NULL, 0);
#else
quota[quota_num].quota.root = name;
do {
r = quota_read("a[quota_num].quota, NULL, 0);
#endif
} while (r == IMAP_AGAIN);
switch (r) {
case 0:
#ifdef APPLE_OS_X_SERVER
quota_list[quota_num++].mail_quota.root = xstrdup(name);
#else
quota[quota_num++].quota.root = xstrdup(name);
#endif
break;
case IMAP_QUOTAROOT_NONEXISTENT:
if (!frock || !quota_num ||
#ifdef APPLE_OS_X_SERVER
strncmp(name, quota_list[quota_num-1].mail_quota.root,
strlen(quota_list[quota_num-1].mail_quota.root))) {
#else
strncmp(name, quota[quota_num-1].quota.root,
strlen(quota[quota_num-1].quota.root))) {
#endif
return 0;
}
break;
default:
return r;
break;
}
if (frock) {
r = fixquota_mailbox(name, matchlen, maycreate, frock);
}
return r;
}
int buildquotalist(char *domain, char **roots, int nroots,
struct fix_rock *frock)
{
int i, r;
char buf[MAX_MAILBOX_NAME+1], *tail;
size_t domainlen = 0;
buf[0] = '\0';
tail = buf;
if (domain) {
domainlen = snprintf(buf, sizeof(buf), "%s!", domain);
tail += domainlen;
}
i = 0;
do {
if (nroots > 0) {
mboxname_hiersep_tointernal("a_namespace, roots[i], 0);
strlcpy(tail, roots[i], sizeof(buf) - domainlen);
}
else {
strlcpy(tail, "*", sizeof(buf) - domainlen);
}
i++;
r = (*quota_namespace.mboxlist_findall)("a_namespace, buf, 1, 0, 0,
&found_match, frock);
if (r < 0) {
errmsg("failed building quota list for '%s'", buf, IMAP_IOERROR);
return IMAP_IOERROR;
}
} while (i < nroots);
return 0;
}
int fixquota_mailbox(char *name,
int matchlen __attribute__((unused)),
int maycreate __attribute__((unused)),
void *rock)
{
int r;
struct mailbox mailbox;
int i, len, thisquota, thisquotalen;
struct fix_rock *frock = (struct fix_rock *) rock;
char *p, *domain = frock->domain;
if (domain &&
(!(p = strchr(name, '!')) || (p - name) != strlen(domain) ||
strncmp(name, domain, p - name))) {
return 0;
}
while (firstquota < quota_num &&
#ifdef APPLE_OS_X_SERVER
strncmp(name, quota_list[firstquota].mail_quota.root,
strlen(quota_list[firstquota].mail_quota.root)) > 0) {
#else
strncmp(name, quota[firstquota].quota.root,
strlen(quota[firstquota].quota.root)) > 0) {
#endif
r = fixquota_finish(firstquota++, frock->tid, &frock->change_count);
if (r) return r;
}
thisquota = -1;
thisquotalen = 0;
for (i = firstquota;
#ifdef APPLE_OS_X_SERVER
i < quota_num && strcmp(name, quota_list[i].mail_quota.root) >= 0; i++) {
len = strlen(quota_list[i].mail_quota.root);
if (!strncmp(name, quota_list[i].mail_quota.root, len) &&
#else
i < quota_num && strcmp(name, quota[i].quota.root) >= 0; i++) {
len = strlen(quota[i].quota.root);
if (!strncmp(name, quota[i].quota.root, len) &&
#endif
(!name[len] || name[len] == '.' ||
(domain && name[len-1] == '!'))) {
#ifdef APPLE_OS_X_SERVER
quota_list[i].refcount++;
#else
quota[i].refcount++;
#endif
if (len > thisquotalen) {
thisquota = i;
thisquotalen = len;
}
}
}
if (partial && thisquota == -1) return 0;
r = mailbox_open_header(name, 0, &mailbox);
if (r) errmsg("failed opening header for mailbox '%s'", name, r);
else {
if (thisquota == -1) {
if (mailbox.quota.root) {
r = fixquota_fixroot(&mailbox, (char *)0);
}
}
else {
if (!mailbox.quota.root ||
#ifdef APPLE_OS_X_SERVER
strcmp(mailbox.quota.root, quota_list[thisquota].mail_quota.root) != 0) {
r = fixquota_fixroot(&mailbox, quota_list[thisquota].mail_quota.root);
#else
strcmp(mailbox.quota.root, quota[thisquota].quota.root) != 0) {
r = fixquota_fixroot(&mailbox, quota[thisquota].quota.root);
#endif
}
if (!r) {
r = mailbox_open_index(&mailbox);
if (r) errmsg("failed opening index for mailbox '%s'", name, r);
}
#ifdef APPLE_OS_X_SERVER
if (!r) quota_list[thisquota].newused += mailbox.quota_mailbox_used;
#else
if (!r) quota[thisquota].newused += mailbox.quota_mailbox_used;
#endif
}
mailbox_close(&mailbox);
}
if (r) {
quota_commit(frock->tid);
*(frock->tid) = NULL;
}
return r;
}
int fixquota_fixroot(struct mailbox *mailbox,
char *root)
{
int r;
redofix = 1;
r = mailbox_lock_header(mailbox);
if (r) {
errmsg("failed locking header for mailbox '%s'", mailbox->name, r);
return r;
}
printf("%s: quota root %s --> %s\n", mailbox->name,
mailbox->quota.root ? mailbox->quota.root : "(none)",
root ? root : "(none)");
if (mailbox->quota.root) free(mailbox->quota.root);
if (root) {
mailbox->quota.root = xstrdup(root);
}
else {
mailbox->quota.root = 0;
}
r = mailbox_write_header(mailbox);
(void) mailbox_unlock_header(mailbox);
if (r) errmsg("failed writing header for mailbox '%s'", mailbox->name, r);
return r;
}
int fixquota_finish(int thisquota, struct txn **tid, unsigned long *count)
{
int r = 0;
#ifdef APPLE_OS_X_SERVER
if (!quota_list[thisquota].refcount) {
if (!quota_list[thisquota].deleted++) {
printf("%s: removed\n", quota_list[thisquota].mail_quota.root);
r = quota_delete("a_list[thisquota].mail_quota, tid);
#else
if (!quota[thisquota].refcount) {
if (!quota[thisquota].deleted++) {
printf("%s: removed\n", quota[thisquota].quota.root);
r = quota_delete("a[thisquota].quota, tid);
#endif
if (r) {
errmsg("failed deleting quotaroot '%s'",
#ifdef APPLE_OS_X_SERVER
quota_list[thisquota].mail_quota.root, r);
#else
quota[thisquota].quota.root, r);
#endif
return r;
}
(*count)++;
#ifdef APPLE_OS_X_SERVER
free(quota_list[thisquota].mail_quota.root);
quota_list[thisquota].mail_quota.root = NULL;
}
return 0;
}
if (quota_list[thisquota].mail_quota.used != quota_list[thisquota].newused) {
#else
free(quota[thisquota].quota.root);
quota[thisquota].quota.root = NULL;
}
return 0;
}
if (quota[thisquota].quota.used != quota[thisquota].newused) {
#endif
#ifdef APPLE_OS_X_SERVER
r = quota_read("a_list[thisquota].mail_quota, tid, 1);
#else
r = quota_read("a[thisquota].quota, tid, 1);
#endif
if (r) {
errmsg("failed reading quotaroot '%s'",
#ifdef APPLE_OS_X_SERVER
quota_list[thisquota].mail_quota.root, r);
return r;
}
(*count)++;
}
if (quota_list[thisquota].mail_quota.used != quota_list[thisquota].newused) {
#else
quota[thisquota].quota.root, r);
return r;
}
(*count)++;
}
if (quota[thisquota].quota.used != quota[thisquota].newused) {
#endif
printf("%s: usage was " UQUOTA_T_FMT ", now " UQUOTA_T_FMT "\n",
#ifdef APPLE_OS_X_SERVER
quota_list[thisquota].mail_quota.root,
quota_list[thisquota].mail_quota.used, quota_list[thisquota].newused);
quota_list[thisquota].mail_quota.used = quota_list[thisquota].newused;
r = quota_write("a_list[thisquota].mail_quota, tid);
#else
quota[thisquota].quota.root,
quota[thisquota].quota.used, quota[thisquota].newused);
quota[thisquota].quota.used = quota[thisquota].newused;
r = quota_write("a[thisquota].quota, tid);
#endif
if (r) {
errmsg("failed writing quotaroot '%s'",
#ifdef APPLE_OS_X_SERVER
quota_list[thisquota].mail_quota.root, r);
#else
quota[thisquota].quota.root, r);
#endif
return r;
}
(*count)++;
}
if (*count && !(*count % 100)) {
quota_commit(tid);
*tid = NULL;
}
return r;
}
int fixquota(struct fix_rock *frock)
{
int i, r = 0;
while (!r && redofix) {
while (!r && firstquota < quota_num) {
r = fixquota_finish(firstquota++, frock->tid, &frock->change_count);
}
redofix = 0;
firstquota = 0;
for (i = 0; !r && i < quota_num; i++) {
r = (*quota_namespace.mboxlist_findall)("a_namespace,
#ifdef APPLE_OS_X_SERVER
quota_list[i].mail_quota.root,
#else
quota[i].quota.root,
#endif
1, 0, 0, fixquota_mailbox,
frock);
}
}
while (!r && firstquota < quota_num) {
r = fixquota_finish(firstquota++, frock->tid, &frock->change_count);
}
if (!r && *(frock->tid)) quota_commit(frock->tid);
return 0;
}
void reportquota(void)
{
int i;
char buf[MAX_MAILBOX_PATH+1];
printf(" Quota %% Used Used Root\n");
for (i = 0; i < quota_num; i++) {
#ifdef APPLE_OS_X_SERVER
if (quota_list[i].deleted) continue;
if (quota_list[i].mail_quota.limit > 0) {
printf(" %7d " QUOTA_REPORT_FMT , quota_list[i].mail_quota.limit,
((quota_list[i].mail_quota.used / QUOTA_UNITS) * 100) / quota_list[i].mail_quota.limit);
}
else if (quota_list[i].mail_quota.limit == 0) {
#else
if (quota[i].deleted) continue;
if (quota[i].quota.limit > 0) {
printf(" %7d " QUOTA_REPORT_FMT , quota[i].quota.limit,
((quota[i].quota.used / QUOTA_UNITS) * 100) / quota[i].quota.limit);
}
else if (quota[i].quota.limit == 0) {
#endif
printf(" 0 ");
}
else {
printf(" ");
}
(*quota_namespace.mboxname_toexternal)("a_namespace,
#ifdef APPLE_OS_X_SERVER
quota_list[i].mail_quota.root,
"_cyrus", buf);
#else
quota[i].quota.root,
"cyrus", buf);
#endif
printf(" " QUOTA_REPORT_FMT " %s\n",
#ifdef APPLE_OS_X_SERVER
quota_list[i].mail_quota.used / QUOTA_UNITS, buf);
#else
quota[i].quota.used / QUOTA_UNITS, buf);
#endif
}
}
#ifdef APPLE_OS_X_SERVER
const char *_dict = " <dict>\n"
" <key>acctLocation</key>\n"
" <string>%s</string>\n"
" <key>diskQuota</key>\n"
" <integer>%lu</integer>\n"
" <key>diskUsed</key>\n"
" <integer>%lu</integer>\n"
" <key>percentFree</key>\n"
" <integer>%lu</integer>\n"
" <key>name</key>\n"
" <string>%s</string>\n"
" </dict>\n";
const char *header = "<dict>\n <key>accountsArray</key>\n <array>\n";
const char *end_array = " </array>\n";
const char *ms_key = " <key>mail_store_size</key>\n <string>%lld</string>\n";
const char *end_dict = "</dict>";
#include "libcyr_cfg.h"
void genquotareport ( void )
{
int i = 0;
char *partition = NULL;
int type = 0;
const char *dbDir = libcyrus_config_getstring( CYRUSOPT_CONFIG_DIR );
FILE *f = NULL;
char userbuf[ MAX_MAILBOX_PATH + 1 ];
char dataBuf[ MAX_MAILBOX_PATH + 1 ];
char filePath[ MAX_MAILBOX_PATH + 1 ];
long long unsigned int total = 0;
if ( !dbDir )
{
return;
}
strcpy( filePath, dbDir );
strcat( filePath, "/quota/quotadata.txt" );
f = fopen( filePath, "w+" );
if ( f )
{
mboxlist_init( 0 );
mboxlist_open( NULL );
lseek( fileno( f ), 0, SEEK_SET );
fwrite( header, sizeof( char ), strlen( header ), f );
for ( i = 0; i < quota_num; i++ )
{
if ( quota_list[ i ].deleted )
{
continue;
}
mboxlist_detail( quota_list[ i ].mail_quota.root, &type, &partition, NULL, NULL, NULL, NULL );
if ( partition == NULL )
{
partition = "default";
}
(*quota_namespace.mboxname_toexternal)
("a_namespace, quota_list[i].mail_quota.root, "_cyrus", userbuf);
char *p = strchr( userbuf, '/' );
if ( p != NULL )
{
p++;
unsigned int quota_limit = 0;
unsigned int quota_used = 0;
unsigned int quota_free = 100;
if ( (quota_list[ i ].mail_quota.limit > 0) && (quota_list[ i ].mail_quota.limit != 2147483647) )
{
quota_limit = quota_list[i].mail_quota.limit * QUOTA_UNITS;
quota_free = 100 - (((quota_list[i].mail_quota.used * QUOTA_UNITS) * 100) / quota_list[i].mail_quota.limit);
}
quota_used = quota_list[i].mail_quota.used;
memset( dataBuf, 0, sizeof( dataBuf ) );
snprintf( dataBuf, sizeof( dataBuf ), _dict, partition, quota_limit, quota_used, quota_free, p );
fwrite( dataBuf, sizeof( char ), strlen( dataBuf ), f );
total+= quota_list[ i ].mail_quota.used;
}
}
fwrite( end_array, sizeof( char ), strlen( end_array ), f );
if ( total != 0 )
{
snprintf( dataBuf, sizeof( dataBuf ), ms_key, (total / QUOTA_UNITS) );
}
else
{
snprintf( dataBuf, sizeof( dataBuf ), ms_key, 0 );
}
fwrite( dataBuf, sizeof( char ), strlen( dataBuf ), f );
fwrite( end_dict, sizeof( char ), strlen( end_dict ), f );
fflush( f );
fclose( f );
mboxlist_close();
mboxlist_done();
}
}
char * get_message_text ( enum imapopt opt )
{
char *fileData = NULL;
const char *filePath = NULL;
FILE *filePtr = NULL;
struct stat sfilestat;
filePath = config_getstring( opt );
if ( filePath != NULL )
{
if ( !stat( filePath, &sfilestat ) )
{
filePtr = fopen( filePath, "r" );
if ( filePtr && (sfilestat.st_size > 0) )
{
fileData = xmalloc( sfilestat.st_size + 1 );
if ( fileData != NULL )
{
if ( read( fileno( filePtr ), fileData, sfilestat.st_size ) != sfilestat.st_size )
{
syslog( LOG_ERR, "Cannot read quota message file: %s", filePath );
free( fileData );
fileData = NULL;
}
else
{
fileData[ sfilestat.st_size ] = '\0';
}
}
else
{
syslog( LOG_ERR, "Quota file memory allocation error" );
}
fclose( filePtr );
}
else
{
syslog( LOG_ERR, "Cannot open quota message \"%s\" for reading.", filePath );
}
}
else
{
syslog( LOG_ERR, "Cannot find quota message file: %s", filePath );
}
}
else
{
syslog( LOG_ERR, "Cannot get quota message path." );
}
return( fileData );
}
void doquotacheck ( void )
{
int i = 0;
int warnLevel = 0;
int status = 0;
pid_t pid = 0;
char *error_txt = NULL;
char *warning_txt = NULL;
int used = 0;
int usage = 0;
int limit = 0;
char *pUser = NULL;
char user[ MAX_MAILBOX_PATH + 1 ];
int fd[ 2 ];
if ( config_getswitch( IMAPOPT_ENABLE_QUOTA_WARNINGS ) )
{
syslog( LOG_INFO, "Starting mail quota warning checks." );
mboxlist_init( 0 );
mboxlist_open( NULL );
warning_txt = get_message_text( IMAPOPT_QUOTA_CUSTOM_WARNING_PATH );
error_txt = get_message_text( IMAPOPT_QUOTA_CUSTOM_ERROR_PATH );
warnLevel = config_getint( IMAPOPT_QUOTAWARN );
for ( i = 0; i < quota_num; i++ )
{
if ( quota_list[ i ].deleted )
{
continue;
}
(*quota_namespace.mboxname_toexternal)( "a_namespace, quota_list[ i ].mail_quota.root, "_cyrus", user );
pUser = strchr( user, '/' );
if ( pUser == NULL )
{
continue;
}
pUser++;
limit = quota_list[ i ].mail_quota.limit;
used = quota_list[i].mail_quota.used / QUOTA_UNITS;
usage = (used * 100) / limit;
syslog( LOG_INFO, "Account: %s; Quota limit: %d MB; Usage: %d MB", pUser, (limit/QUOTA_UNITS), (used/QUOTA_UNITS) );
if ( limit > 0 )
{
if ( usage >= warnLevel )
{
if ( pipe( fd ) >= 0 )
{
pid = fork();
if ( pid == 0 )
{
status = dup2( fd[ 0 ], STDIN_FILENO );
if ( status == -1 )
{
fatal( "deliver: dup2 error", EC_OSERR );
}
close( fd[ 1 ] );
close( fd[ 0 ] );
execlp( "/usr/bin/cyrus/bin/deliver", "deliver", "-q", pUser, NULL );
_exit( 0 );
}
else
{
close( fd[ 0 ] );
if ( usage >= 100 )
{
if ( error_txt != NULL )
{
syslog( LOG_INFO, "Sending quota exceeded error to: %s", pUser );
write( fd[ 1 ], error_txt, strlen( error_txt ) );
}
}
else
{
if ( warning_txt != NULL )
{
write( fd[ 1 ], warning_txt, strlen( warning_txt ) );
syslog( LOG_INFO, "Sending quota usage warning to: %s", pUser );
}
}
close( fd[ 1 ] );
waitpid( pid, &status, 0 );
}
}
else
{
syslog( LOG_DEBUG, "ERROR: pipe failed." );
}
}
}
}
mboxlist_close();
mboxlist_done();
}
else
{
syslog( LOG_WARNING, "Mail quota warnings not enabled" );
}
}
#endif