/* quota.c -- program to report/reconstruct quotas * * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The name "Carnegie Mellon University" must not be used to * endorse or promote products derived from this software without * prior written permission. For permission or any other legal * details, please contact * Office of Technology Transfer * Carnegie Mellon University * 5000 Forbes Avenue * Pittsburgh, PA 15213-3890 * (412) 268-4387, fax: (412) 268-7395 * tech-transfer@andrew.cmu.edu * * 4. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by Computing Services * at Carnegie Mellon University (http://www.cmu.edu/computing/)." * * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ /* $Id: quota.c,v 1.9 2005/03/05 00:37:03 dasenbro Exp $ */ #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_DIRENT_H # include # define NAMLEN(dirent) strlen((dirent)->d_name) #else # define dirent direct # define NAMLEN(dirent) (dirent)->d_namlen # if HAVE_SYS_NDIR_H # include # endif # if HAVE_SYS_DIR_H # include # endif # if HAVE_NDIR_H # include # 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; /* current namespace */ static struct namespace quota_namespace; /* config.c stuff */ const int config_need_data = CONFIG_NEED_PARTITION_DATA; /* forward declarations */ 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 file */ 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); /* Set namespace -- force standard (internal) */ 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 ] [-d ] [-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 restricting our list, see if this quota root matches */ if (frock->nroots) { const char *p; /* skip over domain */ 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; } /* * Add a quota root to the list in 'quota' */ 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; } /* * Build the list of quota roots in 'quota' */ 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; /* Translate separator in mailboxnames. * * We do this directly instead of using the mboxname_tointernal() * function pointer because we know that we are using the internal * namespace and so we don't have to allocate a buffer for the * translated name. */ 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 we have exactly one root specified, narrow the search */ 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; /* get custom warning message path */ filePath = config_getstring( opt ); if ( filePath != NULL ) { /* does the file exist */ if ( !stat( filePath, &sfilestat ) ) { /* open file for reading */ filePtr = fopen( filePath, "r" ); if ( filePtr && (sfilestat.st_size > 0) ) { /* allocate memory for file data */ 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 ); } /* get_message_text */ 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 ]; /* don't do quota check of option not set */ 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 ) { /* send quota warning */ if ( pipe( fd ) >= 0 ) { if ( (pid = fork()) >= 0 ) { if ( pid == 0 ) { /* -- child -- */ /* duplicate pipe's reading end into stdin */ status = dup2( fd[ 0 ], STDIN_FILENO ); if ( status == -1 ) { fatal( "deliver: dup2 error", EC_OSERR ); } /* close file descriptors */ close( fd[ 1 ] ); close( fd[ 0 ] ); /* get deliver path */ 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 { /* Convert internal name to external */ (*quota_namespace.mboxname_toexternal)( "a_namespace, quota_list[ i ].mail_quota.root, "cyrusimap", user ); char *p = strchr( user, '/' ); if ( p ) { /* execute deliver command with user id */ p++; execl( buf, buf, "-q", p, NULL ); } } exit( 1); } else { /* -- parent -- */ /* close file descriptors */ close( STDIN_FILENO ); close( STDOUT_FILENO ); close( fd[ 0 ] ); if ( usage >= 100 ) { /* dump message to the pipe */ if ( errorData != NULL ) { write( fd[ 1 ], errorData, strlen( errorData ) ); } } else { /* dump message to the pipe */ if ( warnData != NULL ) { write( fd[ 1 ], warnData, strlen( warnData ) ); } } /* close the pipe when finished */ close( fd[ 1 ] ); /* wait for it */ (*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 ); } /* doquotacheck */ /* * Account for mailbox 'name' when fixing the quota roots */ 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; /* make sure the domains match */ 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) { /* mailbox error of some type, commit what we have */ 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; } /* * Finish fixing up a quota root */ 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) { /* re-read the quota with the record locked */ 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)++; } /* commit the transaction every 100 changes */ if (*count && !(*count % 100)) { quota_commit(tid); *tid = NULL; } return 0; } /* * Fix all the quota roots */ int fixquota(char *domain, int ispartial) { int r = 0; static char pattern[2] = "*"; struct fix_rock frock; struct txn *tid = NULL; /* * Lock mailbox list to prevent mailbox creation/deletion * during the fix */ 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; } /* * Print out the quota report */ 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(" "); } /* Convert internal name to external */ (*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); } } /* * Print out the quota report */ const char *dict = " \n" " acctLocation\n" " %s\n" " diskQuota\n" " %d\n" " diskUsed\n" " %d\n" " name\n" " %s\n" " \n"; const char *header = "\n accountsArray\n \n"; const char *tailer = " \n"; #include "libcyr_cfg.h" void genquotareport(void) { int i = 0; char *p = NULL; 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; } /* get the partition */ mboxlist_detail( quota_list[ i ].mail_quota.root, &type, NULL, &partition, NULL, NULL ); /* convert internal name to external */ (*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(); } }