| /* |
| * QUOTA An implementation of the diskquota system for the LINUX operating |
| * system. QUOTA is implemented using the BSD systemcall interface |
| * as the means of communication with the user level. Should work for |
| * all filesystems because of integration into the VFS layer of the |
| * operating system. This is based on the Melbourne quota system wich |
| * uses both user and group quota files. |
| * |
| * Program to mail to users that they are over there quota. |
| * |
| * Author: Marco van Wieringen <mvw@planets.elm.net> |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of |
| * the License, or (at your option) any later version. |
| */ |
| |
| #include "config.h" |
| |
| #include <stdio.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <ctype.h> |
| #include <signal.h> |
| #include <grp.h> |
| #include <time.h> |
| #include <getopt.h> |
| #include <locale.h> |
| #ifdef HAVE_NL_LANGINFO |
| #include <langinfo.h> |
| #endif |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <sys/utsname.h> |
| #ifdef USE_LDAP_MAIL_LOOKUP |
| #include <ldap.h> |
| #endif |
| |
| #include "mntopt.h" |
| #include "pot.h" |
| #include "bylabel.h" |
| #include "common.h" |
| #include "quotasys.h" |
| #include "quotaio.h" |
| |
| /* these are just defaults, overridden in the WARNQUOTA_CONF file */ |
| #define MAIL_CMD "/usr/lib/sendmail -t" |
| #define FROM "support@localhost" |
| #define SUBJECT "Disk Quota usage on system" |
| #define CC_TO "root" |
| #define SUPPORT "support@localhost" |
| #define PHONE "(xxx) xxx-xxxx or (yyy) yyy-yyyy" |
| |
| #define DEF_USER_MESSAGE _("Hi,\n\nWe noticed that you are in violation with the quotasystem\n" \ |
| "used on this system. We have found the following violations:\n\n") |
| #define DEF_USER_SIGNATURE _("\nWe hope that you will cleanup before your grace period expires.\n" \ |
| "\nBasically, this means that the system thinks you are using more disk space\n" \ |
| "on the above partition(s) than you are allowed. If you do not delete files\n" \ |
| "and get below your quota before the grace period expires, the system will\n" \ |
| "prevent you from creating new files.\n\n" \ |
| "For additional assistance, please contact us at %s\nor via " \ |
| "phone at %s.\n") |
| #define DEF_GROUP_MESSAGE _("Hi,\n\nWe noticed that the group %s you are member of violates the quotasystem\n" \ |
| "used on this system. We have found the following violations:\n\n") |
| #define DEF_GROUP_SIGNATURE _("\nPlease cleanup the group data before the grace period expires.\n" \ |
| "\nBasically, this means that the system thinks group is using more disk space\n" \ |
| "on the above partition(s) than it is allowed. If you do not delete files\n" \ |
| "and get below group quota before the grace period expires, the system will\n" \ |
| "prevent you and other members of the group from creating new files owned by\n" \ |
| "the group.\n\n" \ |
| "For additional assistance, please contact us at %s\nor via " \ |
| "phone at %s.\n") |
| |
| #define SHELL "/bin/sh" |
| #define QUOTATAB "/etc/quotatab" |
| #define CNF_BUFFER 2048 |
| #define IOBUF_SIZE 16384 /* Size of buffer for line in config files */ |
| #define ADMIN_TAB_ALLOC 256 /* How many entries to admins table should we allocate at once? */ |
| #define WARNQUOTA_CONF "/etc/warnquota.conf" |
| #define ADMINSFILE "/etc/quotagrpadmins" |
| |
| #define FL_USER 1 |
| #define FL_GROUP 2 |
| #define FL_NOAUTOFS 4 |
| #define FL_SHORTNUMS 8 |
| #define FL_NODETAILS 16 |
| |
| struct usage { |
| char *devicename; |
| struct util_dqblk dq_dqb; |
| struct usage *next; |
| }; |
| |
| #ifdef USE_LDAP_MAIL_LOOKUP |
| static LDAP *ldapconn = NULL; |
| #endif |
| |
| struct configparams { |
| char mail_cmd[CNF_BUFFER]; |
| char from[CNF_BUFFER]; |
| char subject[CNF_BUFFER]; |
| char cc_to[CNF_BUFFER]; |
| char support[CNF_BUFFER]; |
| char phone[CNF_BUFFER]; |
| char charset[CNF_BUFFER]; |
| char *user_message; |
| char *user_signature; |
| char *group_message; |
| char *group_signature; |
| time_t cc_before; |
| #ifdef USE_LDAP_MAIL_LOOKUP |
| int use_ldap_mail; /* 0 */ |
| int ldap_is_setup; /* 0 */ |
| int ldap_starttls; /* 0 */ |
| int ldap_tls; /* LDAP_OPT_X_TLS_NEVER */ |
| int ldap_vers; /* LDAP_VERSION3 */ |
| char ldap_host[CNF_BUFFER]; |
| int ldap_port; |
| char ldap_uri[CNF_BUFFER]; |
| char ldap_binddn[CNF_BUFFER]; |
| char ldap_bindpw[CNF_BUFFER]; |
| char ldap_basedn[CNF_BUFFER]; |
| char ldap_search_attr[CNF_BUFFER]; |
| char ldap_mail_attr[CNF_BUFFER]; |
| char default_domain[CNF_BUFFER]; |
| #endif /* USE_LDAP_MAIL_LOOKUP */ |
| }; |
| |
| struct offenderlist { |
| int offender_type; |
| int offender_id; |
| char *offender_name; |
| struct usage *usage; |
| struct offenderlist *next; |
| }; |
| |
| typedef struct quotatable { |
| char *devname; |
| char *devdesc; |
| } quotatable_t; |
| |
| struct adminstable { |
| char *grpname; |
| char *adminname; |
| }; |
| |
| static int qtab_i = 0, fmt = -1, flags; |
| static char maildev[CNF_BUFFER]; |
| static struct quota_handle *maildev_handle; |
| static char *configfile = WARNQUOTA_CONF, *quotatabfile = QUOTATAB, *adminsfile = ADMINSFILE; |
| char *progname; |
| static char *hostname, *domainname; |
| static quotatable_t *quotatable; |
| static int adminscnt, adminsalloc; |
| static struct adminstable *adminstable; |
| |
| /* |
| * Global pointers to list. |
| */ |
| static struct offenderlist *offenders = (struct offenderlist *)0; |
| |
| /* |
| * add any cleanup functions here |
| */ |
| static void wc_exit(int ex_stat) |
| { |
| #ifdef USE_LDAP_MAIL_LOOKUP |
| if(ldapconn != NULL) |
| ldap_unbind_ext(ldapconn, NULL, NULL); |
| #endif |
| exit(ex_stat); |
| } |
| |
| #ifdef USE_LDAP_MAIL_LOOKUP |
| static int setup_ldap(struct configparams *config) |
| { |
| int ret; |
| struct berval cred = { .bv_val = config->ldap_bindpw, |
| .bv_len = strlen(config->ldap_bindpw) }; |
| |
| ret = ldap_initialize(&ldapconn, config->ldap_uri); |
| |
| if (ret != LDAP_SUCCESS) { |
| errstr(_("ldap_initialize() failed: %s\n"), ldap_err2string(ret)); |
| return -1; |
| } |
| |
| if (config->ldap_starttls) { |
| ldap_set_option(ldapconn, LDAP_OPT_PROTOCOL_VERSION, &(config->ldap_vers)); |
| ldap_set_option(ldapconn, LDAP_OPT_X_TLS_REQUIRE_CERT, &(config->ldap_tls)); |
| ret = ldap_start_tls_s(ldapconn, NULL, NULL); |
| if (ret != LDAP_SUCCESS) { |
| errstr(_("ldap_start_tls_s() failed: %s\n"), ldap_err2string(ret)); |
| return -1; |
| } |
| } |
| ret = ldap_sasl_bind_s(ldapconn, config->ldap_binddn, LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL); |
| if(ret < 0) { |
| errstr(_("ldap_sasl_bind_s() failed: %s\n"), ldap_err2string(ret)); |
| return -1; |
| } |
| return 0; |
| } |
| |
| #endif |
| |
| static struct offenderlist *add_offender(int type, int id, char *name) |
| { |
| struct offenderlist *offender; |
| char namebuf[MAXNAMELEN]; |
| |
| if (!name) { |
| if (id2name(id, type, namebuf)) { |
| errstr(_("Cannot get name for uid/gid %u.\n"), id); |
| return NULL; |
| } |
| name = namebuf; |
| } |
| offender = (struct offenderlist *)smalloc(sizeof(struct offenderlist)); |
| offender->offender_type = type; |
| offender->offender_id = id; |
| offender->offender_name = sstrdup(name); |
| offender->usage = (struct usage *)NULL; |
| offender->next = offenders; |
| offenders = offender; |
| return offender; |
| } |
| |
| static void add_offence(struct dquot *dquot, char *name) |
| { |
| struct offenderlist *lptr; |
| struct usage *usage; |
| |
| for (lptr = offenders; lptr; lptr = lptr->next) |
| if (dquot->dq_h->qh_type == lptr->offender_type && lptr->offender_id == dquot->dq_id) |
| break; |
| |
| if (!lptr) |
| if (!(lptr = add_offender(dquot->dq_h->qh_type, dquot->dq_id, name))) |
| return; |
| |
| usage = (struct usage *)smalloc(sizeof(struct usage)); |
| memcpy(&usage->dq_dqb, &dquot->dq_dqb, sizeof(struct util_dqblk)); |
| |
| usage->devicename = sstrdup(dquot->dq_h->qh_quotadev); |
| /* |
| * Stuff it in front |
| */ |
| usage->next = lptr->usage; |
| lptr->usage = usage; |
| } |
| |
| static int deliverable(struct dquot *dquot) |
| { |
| time_t now; |
| struct dquot *mdquot; |
| |
| if (!maildev[0]) |
| return 1; |
| |
| time(&now); |
| |
| if (!strcasecmp(maildev, "any") && |
| ((dquot->dq_dqb.dqb_bhardlimit && toqb(dquot->dq_dqb.dqb_curspace) >= dquot->dq_dqb.dqb_bhardlimit) |
| || ((dquot->dq_dqb.dqb_bsoftlimit && toqb(dquot->dq_dqb.dqb_curspace) >= dquot->dq_dqb.dqb_bsoftlimit) |
| && (dquot->dq_dqb.dqb_btime && dquot->dq_dqb.dqb_btime <= now)))) |
| return 0; |
| if (!maildev_handle) |
| return 1; |
| mdquot = maildev_handle->qh_ops->read_dquot(maildev_handle, dquot->dq_id); |
| if (mdquot && |
| ((mdquot->dq_dqb.dqb_bhardlimit && toqb(mdquot->dq_dqb.dqb_curspace) >= mdquot->dq_dqb.dqb_bhardlimit) |
| || ((mdquot->dq_dqb.dqb_bsoftlimit && toqb(mdquot->dq_dqb.dqb_curspace) >= mdquot->dq_dqb.dqb_bsoftlimit) |
| && (mdquot->dq_dqb.dqb_btime && mdquot->dq_dqb.dqb_btime <= now)))) { |
| free(mdquot); |
| return 0; |
| } |
| free(mdquot); |
| return 1; |
| } |
| |
| static int check_offence(struct dquot *dquot, char *name) |
| { |
| if ((dquot->dq_dqb.dqb_bsoftlimit && toqb(dquot->dq_dqb.dqb_curspace) >= dquot->dq_dqb.dqb_bsoftlimit) |
| || (dquot->dq_dqb.dqb_isoftlimit && dquot->dq_dqb.dqb_curinodes >= dquot->dq_dqb.dqb_isoftlimit)) { |
| if(deliverable(dquot)) |
| add_offence(dquot, name); |
| } |
| return 0; |
| } |
| |
| static FILE *run_mailer(char *command) |
| { |
| int pipefd[2]; |
| FILE *f; |
| |
| if (pipe(pipefd) < 0) { |
| errstr(_("Cannot create pipe: %s\n"), strerror(errno)); |
| return NULL; |
| } |
| signal(SIGPIPE, SIG_IGN); |
| switch(fork()) { |
| case -1: |
| errstr(_("Cannot fork: %s\n"), strerror(errno)); |
| return NULL; |
| case 0: |
| close(pipefd[1]); |
| if (dup2(pipefd[0], 0) < 0) { |
| errstr(_("Cannot duplicate descriptor: %s\n"), strerror(errno)); |
| wc_exit(1); |
| } |
| execl(SHELL, SHELL, "-c", command, NULL); |
| errstr(_("Cannot execute '%s': %s\n"), command, strerror(errno)); |
| wc_exit(1); |
| default: |
| close(pipefd[0]); |
| if (!(f = fdopen(pipefd[1], "w"))) |
| errstr(_("Cannot open pipe: %s\n"), strerror(errno)); |
| return f; |
| } |
| } |
| |
| static int admin_name_cmp(const void *key, const void *mem) |
| { |
| return strcmp(key, ((struct adminstable *)mem)->grpname); |
| } |
| |
| static int should_cc(struct offenderlist *offender, struct configparams *config) |
| { |
| struct usage *lptr; |
| struct util_dqblk *dqb; |
| time_t atime; |
| |
| /* Noone to send CC to? */ |
| if (!strcmp(config->cc_to, "")) |
| return 0; |
| |
| if (config->cc_before == -1) |
| return 1; |
| time(&atime); |
| for (lptr = offender->usage; lptr; lptr = lptr->next) { |
| dqb = &lptr->dq_dqb; |
| if (dqb->dqb_bsoftlimit && dqb->dqb_bsoftlimit <= toqb(dqb->dqb_curspace) && dqb->dqb_btime-config->cc_before <= atime) |
| return 1; |
| if (dqb->dqb_isoftlimit && dqb->dqb_isoftlimit <= dqb->dqb_curinodes && dqb->dqb_itime-config->cc_before <= atime) |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* Substitute %s and %i for 'name' and %h for hostname */ |
| static void format_print(FILE *fp, char *fmt, char *name) |
| { |
| char *ch, *lastch = fmt; |
| |
| for (ch = strchr(fmt, '%'); ch; lastch = ch+2, ch = strchr(ch+2, '%')) { |
| *ch = 0; |
| fputs(lastch, fp); |
| *ch = '%'; |
| switch (*(ch+1)) { |
| case 's': |
| case 'i': |
| fputs(name, fp); |
| break; |
| case 'h': |
| fputs(hostname, fp); |
| break; |
| case 'd': |
| fputs(domainname, fp); |
| break; |
| case '%': |
| fputc('%', fp); |
| break; |
| } |
| } |
| fputs(lastch, fp); |
| } |
| |
| static char *lookup_user(struct configparams *config, char *user) |
| { |
| #ifdef USE_LDAP_MAIL_LOOKUP |
| char searchbuf[256]; |
| LDAPMessage *result, *entry; |
| BerElement *ber = NULL; |
| struct berval **bvals = NULL; |
| int ret, cnt; |
| char *a; |
| char *to = NULL; |
| |
| if (!config->use_ldap_mail) |
| return sstrdup(user); |
| |
| if (ldapconn == NULL && config->ldap_is_setup == 0) { |
| /* need init */ |
| if (setup_ldap(config)) { |
| errstr(_("Could not setup ldap connection.\n")); |
| return NULL; |
| } |
| config->ldap_is_setup = 1; |
| } |
| |
| if (ldapconn == NULL) { |
| /* |
| * ldap was never setup correctly so just use |
| * the offender_name |
| */ |
| return sstrdup(user); |
| } |
| |
| /* search for the offender_name in ldap */ |
| snprintf(searchbuf, 256, "(%s=%s)", config->ldap_search_attr, user); |
| ret = ldap_search_ext_s(ldapconn, |
| config->ldap_basedn, LDAP_SCOPE_SUBTREE, |
| searchbuf, NULL, 0, NULL, NULL, NULL, |
| 0, &result); |
| |
| if (ret < 0) { |
| errstr(_("Error with %s.\n"), user); |
| errstr(_("ldap_search_ext_s() failed: %s\n"), ldap_err2string(ret)); |
| return NULL; |
| } |
| |
| cnt = ldap_count_entries(ldapconn, result); |
| if (cnt > 1) { |
| errstr(_("Multiple entries found for client %s (%d).\n"), |
| user, cnt); |
| return NULL; |
| } else if (cnt == 0) { |
| errstr(_("Entry not found for client %s.\n"), user); |
| return NULL; |
| } |
| /* get the attr */ |
| entry = ldap_first_entry(ldapconn, result); |
| for (a = ldap_first_attribute(ldapconn, entry, &ber); a != NULL; |
| a = ldap_next_attribute(ldapconn, entry, ber)) { |
| if (strcasecmp(a, config->ldap_mail_attr) == 0) { |
| bvals = ldap_get_values_len(ldapconn, entry, a); |
| if (bvals == NULL) { |
| errstr(_("Could not get values for %s.\n"), |
| user); |
| return NULL; |
| } |
| to = sstrdup(bvals[0]->bv_val); |
| ldap_memfree(a); |
| ldap_value_free_len(bvals); |
| break; |
| } |
| ldap_memfree(a); |
| } |
| |
| ber_free(ber, 0); |
| |
| if (to == NULL) { |
| /* |
| * use just the name and default domain as we didn't find the |
| * attribute we wanted in this entry |
| */ |
| to = smalloc(strlen(user) + strlen(config->default_domain) + 1); |
| sprintf(to, "%s@%s", user, config->default_domain); |
| } |
| return to; |
| #else |
| return sstrdup(user); |
| #endif |
| } |
| |
| static int mail_user(struct offenderlist *offender, struct configparams *config) |
| { |
| struct usage *lptr; |
| FILE *fp; |
| int cnt, status; |
| char timebuf[MAXTIMELEN]; |
| char numbuf[3][MAXNUMLEN]; |
| struct util_dqblk *dqb; |
| char *to = NULL; |
| |
| if (offender->offender_type == USRQUOTA) { |
| to = lookup_user(config, offender->offender_name); |
| if (!to) |
| return -1; |
| } else { |
| struct adminstable *admin; |
| |
| if (!(admin = bsearch(offender->offender_name, adminstable, adminscnt, sizeof(struct adminstable), admin_name_cmp))) { |
| errstr(_("Administrator for a group %s not found. Cancelling mail.\n"), offender->offender_name); |
| return -1; |
| } |
| to = sstrdup(admin->adminname); |
| } |
| if (!(fp = run_mailer(config->mail_cmd))) { |
| if (to) |
| free(to); |
| return -1; |
| } |
| fprintf(fp, "From: %s\n", config->from); |
| fprintf(fp, "Reply-To: %s\n", config->support); |
| fprintf(fp, "Subject: %s\n", config->subject); |
| fprintf(fp, "To: %s\n", to); |
| if (should_cc(offender, config)) { |
| char *cc_to = lookup_user(config, config->cc_to); |
| |
| if (cc_to) { |
| fprintf(fp, "Cc: %s\n", config->cc_to); |
| free(cc_to); |
| } |
| } |
| if ((config->charset)[0] != '\0') { /* are we supposed to set the encoding */ |
| fprintf(fp, "MIME-Version: 1.0\n"); |
| fprintf(fp, "Content-Type: text/plain; charset=%s\n", config->charset); |
| fprintf(fp, "Content-Disposition: inline\n"); |
| fprintf(fp, "Content-Transfer-Encoding: 8bit\n"); |
| } |
| fprintf(fp, "\n"); |
| free(to); |
| |
| if (offender->offender_type == USRQUOTA) |
| if (config->user_message) |
| format_print(fp, config->user_message, offender->offender_name); |
| else |
| fputs(DEF_USER_MESSAGE, fp); |
| else |
| if (config->group_message) |
| format_print(fp, config->group_message, offender->offender_name); |
| else |
| fprintf(fp, DEF_GROUP_MESSAGE, offender->offender_name); |
| |
| if (!(flags & FL_NODETAILS)) { |
| for (lptr = offender->usage; lptr; lptr = lptr->next) { |
| dqb = &lptr->dq_dqb; |
| for (cnt = 0; cnt < qtab_i; cnt++) |
| if (!strcmp(quotatable[cnt].devname, lptr->devicename)) { |
| fprintf(fp, "\n%s (%s)\n", quotatable[cnt].devdesc, quotatable[cnt].devname); |
| break; |
| } |
| if (cnt == qtab_i) /* Description not found? */ |
| fprintf(fp, "\n%s\n", lptr->devicename); |
| fprintf(fp, _("\n Block limits File limits\n")); |
| fprintf(fp, _("Filesystem used soft hard grace used soft hard grace\n")); |
| if (strlen(lptr->devicename) > 15) |
| fprintf(fp, "%s\n%15s", lptr->devicename, ""); |
| else |
| fprintf(fp, "%-15s", lptr->devicename); |
| if (dqb->dqb_bsoftlimit && dqb->dqb_bsoftlimit <= toqb(dqb->dqb_curspace)) |
| difftime2str(dqb->dqb_btime, timebuf); |
| else |
| timebuf[0] = '\0'; |
| space2str(toqb(dqb->dqb_curspace), numbuf[0], flags & FL_SHORTNUMS); |
| space2str(dqb->dqb_bsoftlimit, numbuf[1], flags & FL_SHORTNUMS); |
| space2str(dqb->dqb_bhardlimit, numbuf[2], flags & FL_SHORTNUMS); |
| fprintf(fp, "%c%c %7s %7s %7s %6s", |
| dqb->dqb_bsoftlimit && toqb(dqb->dqb_curspace) >= dqb->dqb_bsoftlimit ? '+' : '-', |
| dqb->dqb_isoftlimit && dqb->dqb_curinodes >= dqb->dqb_isoftlimit ? '+' : '-', |
| numbuf[0], numbuf[1], numbuf[2], timebuf); |
| if (dqb->dqb_isoftlimit && dqb->dqb_isoftlimit <= dqb->dqb_curinodes) |
| difftime2str(dqb->dqb_itime, timebuf); |
| else |
| timebuf[0] = '\0'; |
| number2str(dqb->dqb_curinodes, numbuf[0], flags & FL_SHORTNUMS); |
| number2str(dqb->dqb_isoftlimit, numbuf[1], flags & FL_SHORTNUMS); |
| number2str(dqb->dqb_ihardlimit, numbuf[2], flags & FL_SHORTNUMS); |
| fprintf(fp, " %7s %5s %5s %6s\n\n", numbuf[0], numbuf[1], numbuf[2], timebuf); |
| } |
| } |
| |
| |
| if (offender->offender_type == USRQUOTA) |
| if (config->user_signature) |
| format_print(fp, config->user_signature, offender->offender_name); |
| else |
| fprintf(fp, DEF_USER_SIGNATURE, config->support, config->phone); |
| else |
| if (config->group_signature) |
| format_print(fp, config->group_signature, offender->offender_name); |
| else |
| fprintf(fp, DEF_GROUP_SIGNATURE, config->support, config->phone); |
| fclose(fp); |
| if (wait(&status) < 0) /* Wait for mailer */ |
| errstr(_("Cannot wait for mailer: %s\n"), strerror(errno)); |
| else if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) |
| errstr(_("Warning: Mailer exitted abnormally.\n")); |
| |
| return 0; |
| } |
| |
| static int mail_to_offenders(struct configparams *config) |
| { |
| struct offenderlist *lptr; |
| int ret = 0; |
| |
| /* |
| * Dump offenderlist. |
| */ |
| for (lptr = offenders; lptr; lptr = lptr->next) |
| ret |= mail_user(lptr, config); |
| return ret; |
| } |
| |
| /* |
| * Wipe spaces, tabs, quotes and newlines from beginning and end of string |
| */ |
| static void stripstring(char **buff) |
| { |
| int i; |
| |
| /* first put a \0 at the tight place to end the string */ |
| for (i = strlen(*buff) - 1; i >= 0 && (isspace((*buff)[i]) || (*buff)[i] == '"' |
| || (*buff)[i] == '\''); i--); |
| (*buff)[i+1] = 0; |
| |
| /* then determine the position to start */ |
| for (i = 0; (*buff)[i] && (isspace((*buff)[i]) || (*buff)[i] == '"' || (*buff)[i] == '\''); i++); |
| *buff += i; |
| } |
| |
| /* |
| * Substitute '|' with end of lines |
| */ |
| static void create_eoln(char *buf) |
| { |
| char *colpos = buf; |
| |
| while ((colpos = strchr(colpos, '|'))) |
| *colpos = '\n'; |
| } |
| |
| /* |
| * Read /etc/quotatab (description of devices for users) |
| */ |
| static int get_quotatable(void) |
| { |
| FILE *fp; |
| char buffer[IOBUF_SIZE], *colpos, *devname, *devdesc; |
| int line; |
| struct stat st; |
| |
| if (!(fp = fopen(quotatabfile, "r"))) { |
| errstr(_("Cannot open %s: %s\nWill use device names.\n"), quotatabfile, strerror(errno)); |
| qtab_i = 0; |
| return 0; |
| } |
| |
| line = 0; |
| for (qtab_i = 0; quotatable = srealloc(quotatable, sizeof(quotatable_t) * (qtab_i + 1)), |
| fgets(buffer, sizeof(buffer), fp); qtab_i++) { |
| line++; |
| quotatable[qtab_i].devname = NULL; |
| quotatable[qtab_i].devdesc = NULL; |
| if (buffer[0] == '#' || buffer[0] == ';') { /* Comment? */ |
| qtab_i--; |
| continue; |
| } |
| /* Empty line? */ |
| for (colpos = buffer; isspace(*colpos); colpos++); |
| if (!*colpos) { |
| qtab_i--; |
| continue; |
| } |
| /* Parse line */ |
| if (!(colpos = strchr(buffer, ':'))) { |
| errstr(_("Cannot parse line %d in quotatab (missing ':')\n"), line); |
| qtab_i--; |
| continue; |
| } |
| *colpos = 0; |
| devname = buffer; |
| devdesc = colpos+1; |
| stripstring(&devname); |
| stripstring(&devdesc); |
| quotatable[qtab_i].devname = sstrdup(devname); |
| quotatable[qtab_i].devdesc = sstrdup(devdesc); |
| create_eoln(quotatable[qtab_i].devdesc); |
| |
| if (stat(quotatable[qtab_i].devname, &st) < 0) |
| errstr(_("Cannot stat device %s (maybe typo in quotatab)\n"), quotatable[qtab_i].devname); |
| } |
| fclose(fp); |
| return 0; |
| } |
| |
| /* Check correctness of the given format */ |
| static void verify_format(char *fmt, char *varname) |
| { |
| char *ch; |
| |
| for (ch = strchr(fmt, '%'); ch; ch = strchr(ch+2, '%')) { |
| switch (*(ch+1)) { |
| case 's': |
| case 'i': |
| case 'h': |
| case 'd': |
| case '%': |
| continue; |
| default: |
| die(1, _("Incorrect format string for variable %s.\n\ |
| Unrecognized expression %%%c.\n"), varname, *(ch+1)); |
| } |
| } |
| } |
| |
| /* |
| * Reads config parameters from configfile |
| * uses default values if errstr occurs |
| */ |
| static int readconfigfile(const char *filename, struct configparams *config) |
| { |
| FILE *fp; |
| char buff[IOBUF_SIZE]; |
| char *var; |
| char *value; |
| char *pos; |
| int line, len, bufpos; |
| char *locale; |
| |
| /* set default values */ |
| sstrncpy(config->mail_cmd, MAIL_CMD, CNF_BUFFER); |
| sstrncpy(config->from, FROM, CNF_BUFFER); |
| sstrncpy(config->subject, SUBJECT, CNF_BUFFER); |
| sstrncpy(config->cc_to, CC_TO, CNF_BUFFER); |
| sstrncpy(config->support, SUPPORT, CNF_BUFFER); |
| sstrncpy(config->phone, PHONE, CNF_BUFFER); |
| (config->charset)[0] = '\0'; |
| setlocale(LC_ALL, NULL); |
| locale = setlocale(LC_MESSAGES, NULL); |
| #ifdef HAVE_NL_LANGINFO |
| if (locale && strcasecmp(locale, "posix") && strcasecmp(locale, "c")) { |
| locale = nl_langinfo(CODESET); |
| sstrncpy(config->charset, locale, CNF_BUFFER); |
| } |
| #endif |
| maildev[0] = 0; |
| config->user_signature = config->user_message = config->group_signature = config->group_message = NULL; |
| config->cc_before = -1; |
| |
| #ifdef USE_LDAP_MAIL_LOOKUP |
| config->use_ldap_mail = 0; |
| config->ldap_starttls = 0; |
| config->ldap_tls = LDAP_OPT_X_TLS_NEVER; |
| config->ldap_vers = LDAP_VERSION3; |
| config->ldap_port = config->ldap_is_setup = 0; |
| config->ldap_host[0] = 0; |
| config->ldap_uri[0] = 0; |
| #endif |
| |
| if (!(fp = fopen(filename, "r"))) { |
| errstr(_("Cannot open %s: %s\n"), filename, strerror(errno)); |
| return -1; |
| } |
| |
| line = 0; |
| bufpos = 0; |
| while (fgets(buff + bufpos, sizeof(buff) - bufpos, fp)) { /* start reading lines */ |
| line++; |
| |
| if (!bufpos) { |
| /* check for comments or empty lines */ |
| if (buff[0] == '#' || buff[0] == ';') |
| continue; |
| /* Is line empty? */ |
| for (pos = buff; isspace(*pos); pos++); |
| if (!*pos) /* Nothing else was on the line */ |
| continue; |
| } |
| len = bufpos + strlen(buff+bufpos); |
| if (buff[len-1] != '\n') |
| errstr(_("Line %d too long. Truncating.\n"), line); |
| else { |
| len--; |
| if (buff[len-1] == '\\') { /* Should join with next line? */ |
| bufpos = len-1; |
| continue; |
| } |
| } |
| buff[len] = 0; |
| bufpos = 0; |
| |
| /* check for a '=' char */ |
| if ((pos = strchr(buff, '='))) { |
| *pos = 0; /* split buff in two parts: var and value */ |
| var = buff; |
| value = pos + 1; |
| |
| stripstring(&var); |
| stripstring(&value); |
| |
| /* check if var matches anything */ |
| if (!strcmp(var, "MAIL_CMD")) |
| sstrncpy(config->mail_cmd, value, CNF_BUFFER); |
| else if (!strcmp(var, "FROM")) |
| sstrncpy(config->from, value, CNF_BUFFER); |
| else if (!strcmp(var, "SUBJECT")) |
| sstrncpy(config->subject, value, CNF_BUFFER); |
| else if (!strcmp(var, "CC_TO")) |
| sstrncpy(config->cc_to, value, CNF_BUFFER); |
| else if (!strcmp(var, "SUPPORT")) |
| sstrncpy(config->support, value, CNF_BUFFER); |
| else if (!strcmp(var, "PHONE")) |
| sstrncpy(config->phone, value, CNF_BUFFER); |
| else if (!strcmp(var, "CHARSET")) |
| sstrncpy(config->charset, value, CNF_BUFFER); |
| else if (!strcmp(var, "MAILDEV")) |
| /* set the global */ |
| sstrncpy(maildev, value, CNF_BUFFER); |
| else if (!strcmp(var, "MESSAGE")) { |
| config->user_message = sstrdup(value); |
| create_eoln(config->user_message); |
| verify_format(config->user_message, "MESSAGE"); |
| } |
| else if (!strcmp(var, "SIGNATURE")) { |
| config->user_signature = sstrdup(value); |
| create_eoln(config->user_signature); |
| verify_format(config->user_signature, "SIGNATURE"); |
| } |
| else if (!strcmp(var, "GROUP_MESSAGE")) { |
| config->group_message = sstrdup(value); |
| create_eoln(config->group_message); |
| verify_format(config->group_message, "GROUP_MESSAGE"); |
| } |
| else if (!strcmp(var, "GROUP_SIGNATURE")) { |
| config->group_signature = sstrdup(value); |
| create_eoln(config->group_signature); |
| verify_format(config->group_signature, "GROUP_SIGNATURE"); |
| } |
| else if (!strcmp(var, "CC_BEFORE")) { |
| int num; |
| char unit[10]; |
| |
| if (sscanf(value, "%d%s", &num, unit) != 2) |
| goto cc_parse_err; |
| if (str2timeunits(num, unit, &config->cc_before) < 0) { |
| cc_parse_err: |
| die(1, _("Cannot parse time at CC_BEFORE variable (line %d).\n"), line); |
| } |
| } |
| #ifdef USE_LDAP_MAIL_LOOKUP |
| else if (!strcmp(var, "LDAP_MAIL")) { |
| if(strcasecmp(value, "true") == 0) |
| config->use_ldap_mail = 1; |
| else |
| config->use_ldap_mail = 0; |
| } |
| else if (!strcmp(var, "LDAP_TLS")) { |
| if (strcasecmp(value, "never") == 0) { |
| config->ldap_starttls = 1; |
| config->ldap_tls = LDAP_OPT_X_TLS_NEVER; |
| } |
| else if (strcasecmp(value, "demand") == 0) { |
| config->ldap_starttls = 1; |
| config->ldap_tls = LDAP_OPT_X_TLS_DEMAND; |
| } |
| else if (strcasecmp(value, "allow") == 0) { |
| config->ldap_starttls = 1; |
| config->ldap_tls = LDAP_OPT_X_TLS_ALLOW; |
| } |
| else if (strcasecmp(value, "try") == 0) { |
| config->ldap_starttls = 1; |
| config->ldap_tls = LDAP_OPT_X_TLS_TRY; |
| } |
| else |
| config->ldap_starttls = 0; |
| } |
| else if (!strcmp(var, "LDAP_HOST")) |
| sstrncpy(config->ldap_host, value, CNF_BUFFER); |
| else if (!strcmp(var, "LDAP_PORT")) |
| config->ldap_port = (int)strtol(value, NULL, 10); |
| else if (!strcmp(var, "LDAP_URI")) |
| sstrncpy(config->ldap_uri, value, CNF_BUFFER); |
| else if(!strcmp(var, "LDAP_BINDDN")) |
| sstrncpy(config->ldap_binddn, value, CNF_BUFFER); |
| else if(!strcmp(var, "LDAP_BINDPW")) |
| sstrncpy(config->ldap_bindpw, value, CNF_BUFFER); |
| else if(!strcmp(var, "LDAP_BASEDN")) |
| sstrncpy(config->ldap_basedn, value, CNF_BUFFER); |
| else if(!strcmp(var, "LDAP_SEARCH_ATTRIBUTE")) |
| sstrncpy(config->ldap_search_attr, value, CNF_BUFFER); |
| else if(!strcmp(var, "LDAP_MAIL_ATTRIBUTE")) |
| sstrncpy(config->ldap_mail_attr, value, CNF_BUFFER); |
| else if(!strcmp(var, "LDAP_DEFAULT_MAIL_DOMAIN")) |
| sstrncpy(config->default_domain, value, CNF_BUFFER); |
| #endif |
| else /* not matched at all */ |
| errstr(_("Error in config file (line %d), ignoring\n"), line); |
| } |
| else /* no '=' char in this line */ |
| errstr(_("Possible error in config file (line %d), ignoring\n"), line); |
| } |
| if (bufpos) |
| errstr(_("Unterminated last line, ignoring\n")); |
| #ifdef USE_LDAP_MAIL_LOOKUP |
| if (config->use_ldap_mail) |
| { |
| if (!config->ldap_uri[0]) { |
| snprintf(config->ldap_uri, CNF_BUFFER, "ldap://%s:%d", config->ldap_host, config->ldap_port); |
| errstr(_("LDAP library version >= 2.3 detected. Please use LDAP_URI instead of hostname and port.\nGenerated URI %s\n"), config->ldap_uri); |
| } |
| } |
| #endif |
| fclose(fp); |
| |
| return 0; |
| } |
| |
| static int admin_cmp(const void *a1, const void *a2) |
| { |
| return strcmp(((struct adminstable *)a1)->grpname, ((struct adminstable *)a2)->grpname); |
| } |
| |
| /* Get administrators of the groups */ |
| static int get_groupadmins(void) |
| { |
| FILE *f; |
| int line = 0; |
| char buffer[IOBUF_SIZE], *colpos, *grouppos, *endname, *adminpos; |
| |
| if (!(f = fopen(adminsfile, "r"))) { |
| errstr(_("Cannot open file with group administrators: %s\n"), strerror(errno)); |
| return -1; |
| } |
| |
| while (fgets(buffer, IOBUF_SIZE, f)) { |
| line++; |
| if (buffer[0] == ';' || buffer[0] == '#') |
| continue; |
| /* Skip initial spaces */ |
| for (colpos = buffer; isspace(*colpos); colpos++); |
| if (!*colpos) /* Empty line? */ |
| continue; |
| /* Find splitting colon */ |
| for (grouppos = colpos; *colpos && *colpos != ':'; colpos++); |
| if (!*colpos || grouppos == colpos) { |
| errstr(_("Parse error at line %d. Cannot find end of group name.\n"), line); |
| continue; |
| } |
| /* Cut trailing spaces */ |
| for (endname = colpos-1; isspace(*endname); endname--); |
| *(++endname) = 0; |
| /* Skip initial spaces at admins name */ |
| for (colpos++; isspace(*colpos); colpos++); |
| if (!*colpos) { |
| errstr(_("Parse error at line %d. Cannot find administrators name.\n"), line); |
| continue; |
| } |
| /* Go through admins name */ |
| for (adminpos = colpos; !isspace(*colpos); colpos++); |
| if (*colpos) { /* Some characters after name? */ |
| *colpos = 0; |
| /* Skip trailing spaces */ |
| for (colpos++; isspace(*colpos); colpos++); |
| if (*colpos) { |
| errstr(_("Parse error at line %d. Trailing characters after administrators name.\n"), line); |
| continue; |
| } |
| } |
| if (adminscnt >= adminsalloc) |
| adminstable = srealloc(adminstable, sizeof(struct adminstable)*(adminsalloc+=ADMIN_TAB_ALLOC)); |
| adminstable[adminscnt].grpname = sstrdup(grouppos); |
| adminstable[adminscnt++].adminname = sstrdup(adminpos); |
| } |
| |
| fclose(f); |
| qsort(adminstable, adminscnt, sizeof(struct adminstable), admin_cmp); |
| return 0; |
| } |
| |
| static struct quota_handle *find_handle_dev(char *dev, struct quota_handle **handles) |
| { |
| int i; |
| |
| for (i = 0; handles[i] && strcmp(dev, handles[i]->qh_quotadev); i++); |
| return handles[i]; |
| } |
| |
| static void warn_quota(int fs_count, char **fs) |
| { |
| struct quota_handle **handles; |
| struct configparams config; |
| int i; |
| |
| if (readconfigfile(configfile, &config) < 0) |
| wc_exit(1); |
| if (get_quotatable() < 0) |
| wc_exit(1); |
| |
| if (flags & FL_USER) { |
| handles = create_handle_list(fs_count, fs, USRQUOTA, -1, IOI_READONLY | IOI_INITSCAN, MS_LOCALONLY | (flags & FL_NOAUTOFS ? MS_NO_AUTOFS : 0)); |
| if (!maildev[0] || !strcasecmp(maildev, "any")) |
| maildev_handle = NULL; |
| else |
| maildev_handle = find_handle_dev(maildev, handles); |
| for (i = 0; handles[i]; i++) |
| handles[i]->qh_ops->scan_dquots(handles[i], check_offence); |
| dispose_handle_list(handles); |
| } |
| if (flags & FL_GROUP) { |
| if (get_groupadmins() < 0) |
| wc_exit(1); |
| handles = create_handle_list(fs_count, fs, GRPQUOTA, -1, IOI_READONLY | IOI_INITSCAN, MS_LOCALONLY | (flags & FL_NOAUTOFS ? MS_NO_AUTOFS : 0)); |
| if (!maildev[0] || !strcasecmp(maildev, "any")) |
| maildev_handle = NULL; |
| else |
| maildev_handle = find_handle_dev(maildev, handles); |
| for (i = 0; handles[i]; i++) |
| handles[i]->qh_ops->scan_dquots(handles[i], check_offence); |
| dispose_handle_list(handles); |
| } |
| if (mail_to_offenders(&config) < 0) |
| wc_exit(1); |
| } |
| |
| /* Print usage information */ |
| static void usage(void) |
| { |
| errstr(_("Usage:\n warnquota [-ugsid] [-F quotaformat] [-c configfile] [-q quotatabfile] [-a adminsfile] [filesystem...]\n\n\ |
| -u, --user warn users\n\ |
| -g, --group warn groups\n\ |
| -s, --human-readable send information in more human friendly units\n\ |
| -i, --no-autofs avoid autofs mountpoints\n\ |
| -d, --no-details do not send quota information itself\n\ |
| -F, --format=formatname use quotafiles of specific format\n\ |
| -c, --config=config-file non-default config file\n\ |
| -q, --quota-tab=quotatab-file non-default quotatab\n\ |
| -a, --admins-file=admins-file non-default admins file\n\ |
| -h, --help display this help message and exit\n\ |
| -v, --version display version information and exit\n\n")); |
| errstr(_("Bugs to %s\n"), PACKAGE_BUGREPORT); |
| wc_exit(1); |
| } |
| |
| static void parse_options(int argcnt, char **argstr) |
| { |
| int ret; |
| struct option long_opts[] = { |
| { "user", 0, NULL, 'u' }, |
| { "group", 0, NULL, 'g' }, |
| { "version", 0, NULL, 'V' }, |
| { "help", 0, NULL, 'h' }, |
| { "format", 1, NULL, 'F' }, |
| { "config", 1, NULL, 'c' }, |
| { "quota-tab", 1, NULL, 'q' }, |
| { "admins-file", 1, NULL, 'a' }, |
| { "no-autofs", 0, NULL, 'i' }, |
| { "human-readable", 0, NULL, 's' }, |
| { "no-details", 0, NULL, 'd' }, |
| { NULL, 0, NULL, 0 } |
| }; |
| |
| while ((ret = getopt_long(argcnt, argstr, "ugVF:hc:q:a:isd", long_opts, NULL)) != -1) { |
| switch (ret) { |
| case '?': |
| case 'h': |
| usage(); |
| case 'V': |
| version(); |
| exit(0); |
| case 'F': |
| if ((fmt = name2fmt(optarg)) == QF_ERROR) |
| wc_exit(1); |
| break; |
| case 'c': |
| configfile = optarg; |
| break; |
| case 'q': |
| quotatabfile = optarg; |
| break; |
| case 'a': |
| adminsfile = optarg; |
| break; |
| case 'u': |
| flags |= FL_USER; |
| break; |
| case 'g': |
| flags |= FL_GROUP; |
| break; |
| case 'i': |
| flags |= FL_NOAUTOFS; |
| break; |
| case 's': |
| flags |= FL_SHORTNUMS; |
| break; |
| case 'd': |
| flags |= FL_NODETAILS; |
| break; |
| } |
| } |
| if (!(flags & FL_USER) && !(flags & FL_GROUP)) |
| flags |= FL_USER; |
| } |
| |
| static void get_host_name(void) |
| { |
| struct utsname uts; |
| |
| if (uname(&uts)) |
| die(1, _("Cannot get host name: %s\n"), strerror(errno)); |
| hostname = sstrdup(uts.nodename); |
| domainname = sstrdup(uts.domainname); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| gettexton(); |
| progname = basename(argv[0]); |
| get_host_name(); |
| |
| parse_options(argc, argv); |
| init_kernel_interface(); |
| warn_quota(argc - optind, argc > optind ? argv + optind : NULL); |
| |
| wc_exit(0); |
| return 0; |
| } |