#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>
#include <com_err.h>
#include <pwd.h>
#include <sys/wait.h>
#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 "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;
void usage(void);
void reportquota(void);
void genquotareport(void);
int doquotacheck( void );
int buildquotalist(char *domain, char **roots, int nroots);
int fixquota_mailbox(char *name,
int matchlen,
int maycreate,
void *rock);
int fixquota(char *domain, int ispartial);
int fixquota_fixroot(struct mailbox *mailbox,
char *root);
int fixquota_finish(int thisquota, struct txn **tid, unsigned long *count);
struct fix_rock {
char *domain;
struct txn **tid;
unsigned long change_count;
};
struct quotaentry {
struct quota mail_quota;
int refcount;
int deleted;
unsigned long newused;
};
#define QUOTAGROW 300
struct quotaentry *quota_list;
int quota_num = 0, quota_alloc = 0;
int firstquota;
int redofix;
int partial;
int main(int argc,char **argv)
{
int opt;
int fflag = 0;
int qflag = 0;
int rflag = 0;
int r, code = 0;
char *alt_config = NULL, *domain = NULL;
if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
while ((opt = getopt(argc, argv, "C:d:fqr")) != EOF) {
switch (opt) {
case 'C':
alt_config = optarg;
break;
case 'd':
domain = optarg;
break;
case 'f':
fflag = 1;
break;
case 'q':
qflag = 1;
fflag = 1;
break;
case 'r':
rflag = 1;
break;
default:
usage();
}
}
cyrus_init(alt_config, "cyrus-quota", 0);
if ((r = mboxname_init_namespace("a_namespace, 1)) != 0) {
syslog(LOG_ERR, error_message(r));
fatal(error_message(r), EC_CONFIG);
}
quotadb_init(0);
quotadb_open(NULL);
if (!r) r = buildquotalist(domain, argv+optind, argc-optind);
if (!r && fflag) {
mboxlist_init(0);
r = fixquota(domain, argc-optind);
mboxlist_done();
}
quotadb_close();
quotadb_done();
if ( !r )
{
if ( qflag == 1 )
{
r = doquotacheck();
}
else if ( rflag == 1 )
{
genquotareport();
}
else
{
reportquota();
}
}
if (r) {
com_err("cyrus-quota", r, (r == IMAP_IOERROR) ? error_message(errno) : NULL);
code = convert_code(r);
}
cyrus_done();
return code;
}
void usage(void)
{
fprintf(stderr, "usage: cyrus-quota [-C <alt_config>] [-d <domain>] [-f] [prefix]...\n");
exit(EC_USAGE);
}
struct find_rock {
char **roots;
int nroots;
};
static int find_p(void *rockp,
const char *key, int keylen,
const char *data __attribute__((unused)),
int datalen __attribute__((unused)))
{
struct find_rock *frock = (struct find_rock *) rockp;
int i;
if (frock->nroots) {
const char *p;
if (config_virtdomains && (p = strchr(key, '!'))) {
keylen -= (++p - key);
key = p;
}
for (i = 0; i < frock->nroots; i++) {
if (keylen >= strlen(frock->roots[i]) &&
!strncmp(key, frock->roots[i], strlen(frock->roots[i]))) {
break;
}
}
if (i == frock->nroots) return 0;
}
return 1;
}
static int find_cb(void *rockp __attribute__((unused)),
const char *key, int keylen,
const char *data, int datalen __attribute__((unused)))
{
if (!data) return 0;
if (quota_num == quota_alloc) {
quota_alloc += QUOTAGROW;
quota_list = (struct quotaentry *)
xrealloc((char *)quota_list, quota_alloc * sizeof(struct quotaentry));
}
memset("a_list[quota_num], 0, sizeof(struct quotaentry));
quota_list[quota_num].mail_quota.root = xstrndup(key, keylen);
sscanf(data, "%lu %d",
"a_list[quota_num].mail_quota.used, "a_list[quota_num].mail_quota.limit);
quota_num++;
return 0;
}
int buildquotalist(char *domain, char **roots, int nroots)
{
int i;
char buf[MAX_MAILBOX_NAME+1];
struct find_rock frock;
frock.roots = roots;
frock.nroots = nroots;
for (i = 0; i < nroots; i++) {
mboxname_hiersep_tointernal("a_namespace, roots[i], 0);
}
buf[0] = '\0';
if (domain) snprintf(buf, sizeof(buf), "%s!", domain);
if (nroots == 1) strlcat(buf, roots[0], sizeof(buf));
config_quota_db->foreach(qdb, buf, strlen(buf),
&find_p, &find_cb, &frock, NULL);
return 0;
}
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 );
}
int doquotacheck ( void )
{
int r = 0;
int i = 0;
int fd[ 2 ];
int warnLevel = 0;
int status = 0;
pid_t pid = 0;
char *warnData = NULL;
char *errorData = NULL;
int usage = 0;
char user[ MAX_MAILBOX_PATH + 1 ];
char buf[ MAX_MAILBOX_PATH + 1 ];
if ( config_getswitch( IMAPOPT_ENABLE_QUOTA_WARNINGS ) )
{
warnData = get_message_text( IMAPOPT_QUOTA_CUSTOM_WARNING_PATH );
errorData = 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;
}
if ( quota_list[ i ].mail_quota.limit > 0 )
{
usage = ((quota_list[i].mail_quota.used / QUOTA_UNITS) * 100) / quota_list[i].mail_quota.limit;
if ( usage >= warnLevel )
{
if ( pipe( fd ) >= 0 )
{
if ( (pid = fork()) >= 0 )
{
if ( pid == 0 )
{
status = dup2( fd[ 0 ], STDIN_FILENO );
if ( status == -1 )
{
fatal( "deliver: dup2 error", EC_OSERR );
}
close( fd[ 1 ] );
close( fd[ 0 ] );
r = snprintf( buf, sizeof( buf ), "%s/deliver", SERVICE_PATH );
if ( (r < 0) || (r >= sizeof( buf )) )
{
fatal( "deliver command buffer not sufficiently big", EC_CONFIG );
}
else
{
(*quota_namespace.mboxname_toexternal)( "a_namespace, quota_list[ i ].mail_quota.root, "cyrusimap", user );
char *p = strchr( user, '/' );
if ( p )
{
p++;
execl( buf, buf, "-q", p, NULL );
}
}
exit( 1);
}
else
{
close( STDIN_FILENO );
close( STDOUT_FILENO );
close( fd[ 0 ] );
if ( usage >= 100 )
{
if ( errorData != NULL )
{
write( fd[ 1 ], errorData, strlen( errorData ) );
}
}
else
{
if ( warnData != NULL )
{
write( fd[ 1 ], warnData, strlen( warnData ) );
}
}
close( fd[ 1 ] );
(*quota_namespace.mboxname_toexternal)( "a_namespace, quota_list[ i ].mail_quota.root, "cyrusimap", user );
if ( usage >= 100 )
{
syslog( LOG_INFO, "User account \"%s\" has exceeded quota.", user );
}
else
{
syslog( LOG_INFO, "User account \"%s\" is approaching quota.", user );
}
waitpid( pid, &status, 0 );
}
}
}
}
}
}
}
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 &&
strncmp(name, quota_list[firstquota].mail_quota.root,
strlen(quota_list[firstquota].mail_quota.root)) > 0) {
r = fixquota_finish(firstquota++, frock->tid, &frock->change_count);
if (r) return r;
}
thisquota = -1;
thisquotalen = 0;
for (i = firstquota;
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) &&
(!name[len] || name[len] == '.' ||
(domain && name[len-1] == '!'))) {
quota_list[i].refcount++;
if (len > thisquotalen) {
thisquota = i;
thisquotalen = len;
}
}
}
if (partial && thisquota == -1) return 0;
r = mailbox_open_header(name, 0, &mailbox);
if (!r) {
if (thisquota == -1) {
if (mailbox.quota.root) {
r = fixquota_fixroot(&mailbox, (char *)0);
}
}
else {
if (!mailbox.quota.root ||
strcmp(mailbox.quota.root, quota_list[thisquota].mail_quota.root) != 0) {
r = fixquota_fixroot(&mailbox, quota_list[thisquota].mail_quota.root);
}
if (!r) r = mailbox_open_index(&mailbox);
if (!r) quota_list[thisquota].newused += mailbox.quota_mailbox_used;
}
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) 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);
return r;
}
int fixquota_finish(int thisquota, struct txn **tid, unsigned long *count)
{
int r;
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);
if (r) return r;
(*count)++;
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) {
r = quota_read("a_list[thisquota].mail_quota, tid, 1);
if (r) return r;
(*count)++;
}
if (quota_list[thisquota].mail_quota.used != quota_list[thisquota].newused) {
printf("%s: usage was %lu, now %lu\n", 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);
if (r) return r;
(*count)++;
}
if (*count && !(*count % 100)) {
quota_commit(tid);
*tid = NULL;
}
return 0;
}
int fixquota(char *domain, int ispartial)
{
int r = 0;
static char pattern[2] = "*";
struct fix_rock frock;
struct txn *tid = NULL;
mboxlist_open(NULL);
frock.domain = domain;
frock.tid = &tid;
frock.change_count = 0;
redofix = 1;
while (!r && redofix) {
redofix = 0;
firstquota = 0;
partial = ispartial;
r = (*quota_namespace.mboxlist_findall)("a_namespace, pattern, 1,
0, 0, fixquota_mailbox, &frock);
while (!r && firstquota < quota_num) {
r = fixquota_finish(firstquota++, &tid, &frock.change_count);
}
}
if (!r && tid) quota_commit(&tid);
mboxlist_close();
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++) {
if (quota_list[i].deleted) continue;
if (quota_list[i].mail_quota.limit > 0) {
printf(" %7d %7ld", 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) {
printf(" 0 ");
}
else {
printf(" ");
}
(*quota_namespace.mboxname_toexternal)("a_namespace,
quota_list[i].mail_quota.root,
"cyrusimap", buf);
printf(" %7ld %s\n", quota_list[i].mail_quota.used / QUOTA_UNITS, buf);
}
}
const char *dict = " <dict>\n"
" <key>acctLocation</key>\n"
" <string>%s</string>\n"
" <key>diskQuota</key>\n"
" <integer>%d</integer>\n"
" <key>diskUsed</key>\n"
" <integer>%d</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 *tailer = " </array>\n</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 tmpbuf[ MAX_MAILBOX_PATH + 1 ];
char filePath[ MAX_MAILBOX_PATH + 1 ];
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, NULL, &partition, NULL, NULL );
(*quota_namespace.mboxname_toexternal)
("a_namespace, quota_list[i].mail_quota.root, "cyrusimap", userbuf);
char *p = strchr( userbuf, '/' );
if ( p )
{
p++;
memset( tmpbuf, 0, sizeof( tmpbuf ) );
if ( (quota_list[ i ].mail_quota.limit == 0) || (quota_list[ i ].mail_quota.limit == 2147483647) )
{
snprintf( tmpbuf, sizeof( tmpbuf ), dict, partition, 0, quota_list[ i ].mail_quota.used, p );
}
else
{
snprintf( tmpbuf, sizeof( tmpbuf ), dict, partition, quota_list[ i ].mail_quota.limit * QUOTA_UNITS, quota_list[ i ].mail_quota.used, p );
}
fwrite( tmpbuf, sizeof( char ), strlen( tmpbuf ), f );
}
}
fwrite( tailer, sizeof( char ), strlen( tailer ), f );
fflush( f );
fclose( f );
mboxlist_close();
mboxlist_done();
}
}