Update to 4.01.
[profile/ivi/quota.git] / warnquota.c
1 /*
2  * QUOTA    An implementation of the diskquota system for the LINUX operating
3  *          system. QUOTA is implemented using the BSD systemcall interface
4  *          as the means of communication with the user level. Should work for
5  *          all filesystems because of integration into the VFS layer of the
6  *          operating system. This is based on the Melbourne quota system wich
7  *          uses both user and group quota files.
8  * 
9  *          Program to mail to users that they are over there quota.
10  * 
11  * Author:  Marco van Wieringen <mvw@planets.elm.net>
12  *
13  *          This program is free software; you can redistribute it and/or
14  *          modify it under the terms of the GNU General Public License as
15  *          published by the Free Software Foundation; either version 2 of
16  *          the License, or (at your option) any later version.
17  */
18
19 #include "config.h"
20
21 #include <stdio.h>
22 #include <fcntl.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <ctype.h>
28 #include <signal.h>
29 #include <grp.h>
30 #include <time.h>
31 #include <getopt.h>
32 #include <sys/types.h>
33 #include <sys/wait.h>
34 #include <sys/utsname.h>
35 #ifdef USE_LDAP_MAIL_LOOKUP
36 #include <ldap.h>
37 #endif
38
39 #include "mntopt.h"
40 #include "pot.h"
41 #include "bylabel.h"
42 #include "common.h"
43 #include "quotasys.h"
44 #include "quotaio.h"
45
46 /* these are just defaults, overridden in the WARNQUOTA_CONF file */
47 #define MAIL_CMD "/usr/lib/sendmail -t"
48 #define FROM     "support@localhost"
49 #define SUBJECT  "Disk Quota usage on system"
50 #define CC_TO    "root"
51 #define SUPPORT  "support@localhost"
52 #define PHONE    "(xxx) xxx-xxxx or (yyy) yyy-yyyy"
53
54 #define DEF_USER_MESSAGE        _("Hi,\n\nWe noticed that you are in violation with the quotasystem\n" \
55                           "used on this system. We have found the following violations:\n\n")
56 #define DEF_USER_SIGNATURE      _("\nWe hope that you will cleanup before your grace period expires.\n" \
57                           "\nBasically, this means that the system thinks you are using more disk space\n" \
58                           "on the above partition(s) than you are allowed.  If you do not delete files\n" \
59                           "and get below your quota before the grace period expires, the system will\n" \
60                           "prevent you from creating new files.\n\n" \
61                           "For additional assistance, please contact us at %s\nor via " \
62                           "phone at %s.\n")
63 #define DEF_GROUP_MESSAGE       _("Hi,\n\nWe noticed that the group %s you are member of violates the quotasystem\n" \
64                           "used on this system. We have found the following violations:\n\n")
65 #define DEF_GROUP_SIGNATURE     _("\nPlease cleanup the group data before the grace period expires.\n" \
66                           "\nBasically, this means that the system thinks group is using more disk space\n" \
67                           "on the above partition(s) than it is allowed.  If you do not delete files\n" \
68                           "and get below group quota before the grace period expires, the system will\n" \
69                           "prevent you and other members of the group from creating new files owned by\n" \
70                           "the group.\n\n" \
71                           "For additional assistance, please contact us at %s\nor via " \
72                           "phone at %s.\n")
73
74 #define SHELL "/bin/sh"
75 #define QUOTATAB "/etc/quotatab"
76 #define CNF_BUFFER 2048
77 #define IOBUF_SIZE 16384                /* Size of buffer for line in config files */
78 #define ADMIN_TAB_ALLOC 256             /* How many entries to admins table should we allocate at once? */
79 #define WARNQUOTA_CONF "/etc/warnquota.conf"
80 #define ADMINSFILE "/etc/quotagrpadmins"
81
82 #define FL_USER 1
83 #define FL_GROUP 2
84 #define FL_NOAUTOFS 4
85 #define FL_SHORTNUMS 8
86 #define FL_NODETAILS 16
87
88 struct usage {
89         char *devicename;
90         struct util_dqblk dq_dqb;
91         struct usage *next;
92 };
93
94 #ifdef USE_LDAP_MAIL_LOOKUP
95 static LDAP *ldapconn = NULL;
96 #endif
97
98 struct configparams {
99         char mail_cmd[CNF_BUFFER];
100         char from[CNF_BUFFER];
101         char subject[CNF_BUFFER];
102         char cc_to[CNF_BUFFER];
103         char support[CNF_BUFFER];
104         char phone[CNF_BUFFER];
105         char charset[CNF_BUFFER];
106         char *user_message;
107         char *user_signature;
108         char *group_message;
109         char *group_signature;
110         int use_ldap_mail; /* 0 */
111         time_t cc_before;
112 #ifdef USE_LDAP_MAIL_LOOKUP
113         int ldap_is_setup; /* 0 */
114         char ldap_host[CNF_BUFFER];
115         int ldap_port;
116         char ldap_uri[CNF_BUFFER];
117         char ldap_binddn[CNF_BUFFER];
118         char ldap_bindpw[CNF_BUFFER];
119         char ldap_basedn[CNF_BUFFER];
120         char ldap_search_attr[CNF_BUFFER];
121         char ldap_mail_attr[CNF_BUFFER];
122         char default_domain[CNF_BUFFER];
123 #endif /* USE_LDAP_MAIL_LOOKUP */
124 };
125
126 struct offenderlist {
127         int offender_type;
128         int offender_id;
129         char *offender_name;
130         struct usage *usage;
131         struct offenderlist *next;
132 };
133
134 typedef struct quotatable {
135         char *devname;
136         char *devdesc;
137 } quotatable_t;
138
139 struct adminstable {
140         char *grpname;
141         char *adminname;
142 };
143
144 static int qtab_i = 0, fmt = -1, flags;
145 static char maildev[CNF_BUFFER];
146 static struct quota_handle *maildev_handle;
147 static char *configfile = WARNQUOTA_CONF, *quotatabfile = QUOTATAB, *adminsfile = ADMINSFILE;
148 char *progname;
149 static char *hostname, *domainname;
150 static quotatable_t *quotatable;
151 static int adminscnt, adminsalloc;
152 static struct adminstable *adminstable;
153
154 /*
155  * Global pointers to list.
156  */
157 static struct offenderlist *offenders = (struct offenderlist *)0;
158
159 /*
160  * add any cleanup functions here
161  */
162 static void wc_exit(int ex_stat)
163 {
164 #ifdef USE_LDAP_MAIL_LOOKUP
165         if(ldapconn != NULL)
166 #ifdef USE_LDAP_23
167                 ldap_unbind_ext(ldapconn, NULL, NULL);
168 #else
169                 ldap_unbind(ldapconn);
170 #endif
171 #endif
172         exit(ex_stat);
173 }
174
175 #ifdef USE_LDAP_MAIL_LOOKUP
176 #ifdef NEED_LDAP_PERROR
177 static void ldap_perror(LDAP *ld, LDAP_CONST char *s)
178 {
179         int err;
180
181         ldap_get_option(ld, LDAP_OPT_RESULT_CODE, &err);
182         errstr(_("%s: %s\n"), s, ldap_err2string(err));
183 }
184 #endif
185
186 static int setup_ldap(struct configparams *config)
187 {
188         int ret;
189 #ifdef USE_LDAP_23
190         struct berval cred = { .bv_val = config->ldap_bindpw,
191                                .bv_len = strlen(config->ldap_bindpw) };
192 #endif
193
194 #ifdef USE_LDAP_23
195         ldap_initialize(&ldapconn, config->ldap_uri);
196 #else
197         ldapconn = ldap_init(config->ldap_host, config->ldap_port);
198 #endif
199
200         if(ldapconn == NULL) {
201                 ldap_perror(ldapconn, "ldap_init");
202                 return -1;
203         }
204
205 #ifdef USE_LDAP_23
206         ret = ldap_sasl_bind_s(ldapconn, config->ldap_binddn, LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL);
207 #else
208         ret = ldap_bind_s(ldapconn, config->ldap_binddn, config->ldap_bindpw, LDAP_AUTH_SIMPLE);
209 #endif
210         if(ret < 0) {
211                 ldap_perror(ldapconn, "ldap_bind");
212                 return -1;
213         }
214         return 0;
215 }
216                 
217 #endif
218
219 static struct offenderlist *add_offender(int type, int id, char *name)
220 {
221         struct offenderlist *offender;
222         char namebuf[MAXNAMELEN];
223         
224         if (!name) {
225                 if (id2name(id, type, namebuf)) {
226                         errstr(_("Cannot get name for uid/gid %u.\n"), id);
227                         return NULL;
228                 }
229                 name = namebuf;
230         }
231         offender = (struct offenderlist *)smalloc(sizeof(struct offenderlist));
232         offender->offender_type = type;
233         offender->offender_id = id;
234         offender->offender_name = sstrdup(name);
235         offender->usage = (struct usage *)NULL;
236         offender->next = offenders;
237         offenders = offender;
238         return offender;
239 }
240
241 static void add_offence(struct dquot *dquot, char *name)
242 {
243         struct offenderlist *lptr;
244         struct usage *usage;
245
246         for (lptr = offenders; lptr; lptr = lptr->next)
247                 if (dquot->dq_h->qh_type == lptr->offender_type && lptr->offender_id == dquot->dq_id)
248                         break;
249
250         if (!lptr)
251                 if (!(lptr = add_offender(dquot->dq_h->qh_type, dquot->dq_id, name)))
252                         return;
253
254         usage = (struct usage *)smalloc(sizeof(struct usage));
255         memcpy(&usage->dq_dqb, &dquot->dq_dqb, sizeof(struct util_dqblk));
256
257         usage->devicename = sstrdup(dquot->dq_h->qh_quotadev);
258         /*
259          * Stuff it in front
260          */
261         usage->next = lptr->usage;
262         lptr->usage = usage;
263 }
264
265 static int deliverable(struct dquot *dquot)
266 {
267         time_t now;
268         struct dquot *mdquot;
269         
270         if (!maildev[0])
271                 return 1;
272
273         time(&now);
274         
275         if (!strcasecmp(maildev, "any") && 
276            ((dquot->dq_dqb.dqb_bhardlimit && toqb(dquot->dq_dqb.dqb_curspace) >= dquot->dq_dqb.dqb_bhardlimit)
277            || ((dquot->dq_dqb.dqb_bsoftlimit && toqb(dquot->dq_dqb.dqb_curspace) >= dquot->dq_dqb.dqb_bsoftlimit)
278            && (dquot->dq_dqb.dqb_btime && dquot->dq_dqb.dqb_btime <= now))))
279                 return 0;
280         if (!maildev_handle)
281                 return 1;
282         mdquot = maildev_handle->qh_ops->read_dquot(maildev_handle, dquot->dq_id);
283         if (mdquot &&
284            ((mdquot->dq_dqb.dqb_bhardlimit && toqb(mdquot->dq_dqb.dqb_curspace) >= mdquot->dq_dqb.dqb_bhardlimit)
285            || ((mdquot->dq_dqb.dqb_bsoftlimit && toqb(mdquot->dq_dqb.dqb_curspace) >= mdquot->dq_dqb.dqb_bsoftlimit)
286            && (mdquot->dq_dqb.dqb_btime && mdquot->dq_dqb.dqb_btime <= now)))) {
287                 free(mdquot);
288                 return 0;
289         }
290         free(mdquot);
291         return 1;
292 }
293
294 static int check_offence(struct dquot *dquot, char *name)
295 {
296         if ((dquot->dq_dqb.dqb_bsoftlimit && toqb(dquot->dq_dqb.dqb_curspace) >= dquot->dq_dqb.dqb_bsoftlimit)
297             || (dquot->dq_dqb.dqb_isoftlimit && dquot->dq_dqb.dqb_curinodes >= dquot->dq_dqb.dqb_isoftlimit)) {
298                 if(deliverable(dquot))
299                         add_offence(dquot, name);
300         }
301         return 0;
302 }
303
304 static FILE *run_mailer(char *command)
305 {
306         int pipefd[2];
307         FILE *f;
308
309         if (pipe(pipefd) < 0) {
310                 errstr(_("Cannot create pipe: %s\n"), strerror(errno));
311                 return NULL;
312         }
313         signal(SIGPIPE, SIG_IGN);
314         switch(fork()) {
315                 case -1:
316                         errstr(_("Cannot fork: %s\n"), strerror(errno));
317                         return NULL;
318                 case 0:
319                         close(pipefd[1]);
320                         if (dup2(pipefd[0], 0) < 0) {
321                                 errstr(_("Cannot duplicate descriptor: %s\n"), strerror(errno));
322                                 wc_exit(1);
323                         }                       
324                         execl(SHELL, SHELL, "-c", command, NULL);
325                         errstr(_("Cannot execute '%s': %s\n"), command, strerror(errno));
326                         wc_exit(1);
327                 default:
328                         close(pipefd[0]);
329                         if (!(f = fdopen(pipefd[1], "w")))
330                                 errstr(_("Cannot open pipe: %s\n"), strerror(errno));
331                         return f;
332         }
333 }
334
335 static int admin_name_cmp(const void *key, const void *mem)
336 {
337         return strcmp(key, ((struct adminstable *)mem)->grpname);
338 }
339
340 static int should_cc(struct offenderlist *offender, struct configparams *config)
341 {
342         struct usage *lptr;
343         struct util_dqblk *dqb;
344         time_t atime;
345
346         if (config->cc_before == -1)
347                 return 1;
348         time(&atime);
349         for (lptr = offender->usage; lptr; lptr = lptr->next) {
350                 dqb = &lptr->dq_dqb;
351                 if (dqb->dqb_bsoftlimit && dqb->dqb_bsoftlimit <= toqb(dqb->dqb_curspace) && dqb->dqb_btime-config->cc_before <= atime)
352                         return 1;
353                 if (dqb->dqb_isoftlimit && dqb->dqb_isoftlimit <= dqb->dqb_curinodes && dqb->dqb_itime-config->cc_before <= atime)
354                         return 1;
355         }
356         return 0;
357 }
358
359 /* Substitute %s and %i for 'name' and %h for hostname */
360 static void format_print(FILE *fp, char *fmt, char *name)
361 {
362         char *ch, *lastch = fmt;
363
364         for (ch = strchr(fmt, '%'); ch; lastch = ch+2, ch = strchr(ch+2, '%')) {
365                 *ch = 0;
366                 fputs(lastch, fp);
367                 *ch = '%';
368                 switch (*(ch+1)) {
369                         case 's':
370                         case 'i':
371                                 fputs(name, fp);
372                                 break;
373                         case 'h':
374                                 fputs(hostname, fp);
375                                 break;
376                         case 'd':
377                                 fputs(domainname, fp);
378                                 break;
379                         case '%':
380                                 fputc('%', fp);
381                                 break;
382                 }
383         }
384         fputs(lastch, fp);
385 }
386
387 static int mail_user(struct offenderlist *offender, struct configparams *config)
388 {
389         struct usage *lptr;
390         FILE *fp;
391         int cnt, status;
392         char timebuf[MAXTIMELEN];
393         char numbuf[3][MAXNUMLEN];
394         struct util_dqblk *dqb;
395         char *to = NULL;
396 #ifdef USE_LDAP_MAIL_LOOKUP
397         char searchbuf[256];
398         LDAPMessage *result, *entry;
399         BerElement     *ber = NULL;
400         struct berval  **bvals = NULL;
401         int ret;
402         char *a;
403 #endif
404
405         if (offender->offender_type == USRQUOTA) {
406 #ifdef USE_LDAP_MAIL_LOOKUP
407                 if(config->use_ldap_mail != 0) {
408                         if((ldapconn == NULL) && (config->ldap_is_setup == 0)) {
409                                 /* need init */
410                                 if(setup_ldap(config)) {
411                                         errstr(_("Could not setup ldap connection, returning.\n"));
412                                         return -1;
413                                 }
414                                 config->ldap_is_setup = 1;
415                         }
416
417                         if(ldapconn == NULL) {
418                                 /* ldap was never setup correctly so just use the offender_name */
419                                 to = sstrdup(offender->offender_name);
420                         } else {
421                                 /* search for the offender_name in ldap */
422                                 snprintf(searchbuf, 256, "(%s=%s)", config->ldap_search_attr, 
423                                         offender->offender_name);
424 #ifdef USE_LDAP_23
425                                 ret = ldap_search_ext_s(ldapconn, config->ldap_basedn, 
426                                         LDAP_SCOPE_SUBTREE, searchbuf,
427                                         NULL, 0, NULL, NULL, NULL, 0, &result);
428 #else
429                                 ret = ldap_search_s(ldapconn, config->ldap_basedn, 
430                                         LDAP_SCOPE_SUBTREE, searchbuf,
431                                         NULL, 0, &result);
432 #endif
433                                 if(ret < 0) {
434                                         errstr(_("Error with %s.\n"), offender->offender_name);
435                                         ldap_perror(ldapconn, "ldap_search");
436                                         return 0;
437                                 }
438                                         
439                                 cnt = ldap_count_entries(ldapconn, result);
440
441                                 if(cnt > 1) {
442                                         errstr(_("Multiple entries found for client %s (%d). Not sending mail.\n"), 
443                                                 offender->offender_name, cnt);
444                                         return 0;
445                                 } else if(cnt == 0) {
446                                         errstr(_("Entry not found for client %s. Not sending mail.\n"), 
447                                                 offender->offender_name);
448                                         return 0;
449                                 } else {
450                                         /* get the attr */
451                                         entry = ldap_first_entry(ldapconn, result);
452                                         for(a = ldap_first_attribute(ldapconn, entry, &ber); a != NULL;
453                                                 a = ldap_next_attribute( ldapconn, entry, ber)) {
454                                                 if(strcasecmp(a, config->ldap_mail_attr) == 0) {
455                                                         bvals = ldap_get_values_len(ldapconn, entry, a);
456                                                         if(bvals == NULL) {
457                                                                 errstr(_("Could not get values for %s.\n"), 
458                                                                         offender->offender_name);
459                                                                 return 0;
460                                                         }
461                                                         to = sstrdup(bvals[0]->bv_val);
462                                                         break;
463                                                 } 
464                                         } 
465
466                                         ber_bvecfree(bvals);
467                                         if(to == NULL) {
468                                                 /* 
469                                                  * use just the name and default domain as we didn't find the
470                                                  * attribute we wanted in this entry
471                                                  */
472                                                 to = malloc(strlen(offender->offender_name)+
473                                                         strlen(config->default_domain)+1);
474                                                 sprintf(to, "%s@%s", offender->offender_name,
475                                                         config->default_domain);
476                                         }
477                                 }
478                         }
479                 } else {
480                         to = sstrdup(offender->offender_name);
481                 }
482 #else
483                 to = sstrdup(offender->offender_name);
484 #endif
485         } else {
486                 struct adminstable *admin;
487
488                 if (!(admin = bsearch(offender->offender_name, adminstable, adminscnt, sizeof(struct adminstable), admin_name_cmp))) {
489                         errstr(_("Administrator for a group %s not found. Cancelling mail.\n"), offender->offender_name);
490                         return -1;
491                 }
492                 to = sstrdup(admin->adminname);
493         }
494         if (!(fp = run_mailer(config->mail_cmd))) {
495                 if(to)
496                         free(to);
497                 return -1;
498         }
499         fprintf(fp, "From: %s\n", config->from);
500         fprintf(fp, "Reply-To: %s\n", config->support);
501         fprintf(fp, "Subject: %s\n", config->subject);
502         fprintf(fp, "To: %s\n", to);
503         if (should_cc(offender, config))
504                 fprintf(fp, "Cc: %s\n", config->cc_to);
505         if ((config->charset)[0] != '\0') { /* are we supposed to set the encoding */
506                 fprintf(fp, "Content-Type: text/plain; charset=%s\n", config->charset);
507                 fprintf(fp, "Content-Disposition: inline\n");
508                 fprintf(fp, "Content-Transfer-Encoding: 8bit\n");
509         }
510         fprintf(fp, "\n");
511         free(to);
512
513         if (offender->offender_type == USRQUOTA)
514                 if (config->user_message)
515                         format_print(fp, config->user_message, offender->offender_name);
516                 else
517                         fputs(DEF_USER_MESSAGE, fp);
518         else
519                 if (config->group_message)
520                         format_print(fp, config->group_message, offender->offender_name);
521                 else
522                         fprintf(fp, DEF_GROUP_MESSAGE, offender->offender_name);
523
524         if (!(flags & FL_NODETAILS)) {
525                 for (lptr = offender->usage; lptr; lptr = lptr->next) {
526                         dqb = &lptr->dq_dqb;
527                         for (cnt = 0; cnt < qtab_i; cnt++)
528                                 if (!strcmp(quotatable[cnt].devname, lptr->devicename)) {
529                                         fprintf(fp, "\n%s (%s)\n", quotatable[cnt].devdesc, quotatable[cnt].devname);
530                                         break;
531                                 }
532                         if (cnt == qtab_i)      /* Description not found? */
533                                 fprintf(fp, "\n%s\n", lptr->devicename);
534                         fprintf(fp, _("\n                        Block limits               File limits\n"));
535                         fprintf(fp, _("Filesystem           used    soft    hard  grace    used  soft  hard  grace\n"));
536                         if (strlen(lptr->devicename) > 15)
537                                 fprintf(fp, "%s\n%15s", lptr->devicename, "");
538                         else
539                                 fprintf(fp, "%-15s", lptr->devicename);
540                         if (dqb->dqb_bsoftlimit && dqb->dqb_bsoftlimit <= toqb(dqb->dqb_curspace))
541                                 difftime2str(dqb->dqb_btime, timebuf);
542                         else
543                                 timebuf[0] = '\0';
544                         space2str(toqb(dqb->dqb_curspace), numbuf[0], flags & FL_SHORTNUMS);
545                         space2str(dqb->dqb_bsoftlimit, numbuf[1], flags & FL_SHORTNUMS);
546                         space2str(dqb->dqb_bhardlimit, numbuf[2], flags & FL_SHORTNUMS);
547                         fprintf(fp, "%c%c %7s %7s %7s %6s",
548                                 dqb->dqb_bsoftlimit && toqb(dqb->dqb_curspace) >= dqb->dqb_bsoftlimit ? '+' : '-',
549                                 dqb->dqb_isoftlimit && dqb->dqb_curinodes >= dqb->dqb_isoftlimit ? '+' : '-',
550                                 numbuf[0], numbuf[1], numbuf[2], timebuf);
551                         if (dqb->dqb_isoftlimit && dqb->dqb_isoftlimit <= dqb->dqb_curinodes)
552                                 difftime2str(dqb->dqb_itime, timebuf);
553                         else
554                                 timebuf[0] = '\0';
555                         number2str(dqb->dqb_curinodes, numbuf[0], flags & FL_SHORTNUMS);
556                         number2str(dqb->dqb_isoftlimit, numbuf[1], flags & FL_SHORTNUMS);
557                         number2str(dqb->dqb_ihardlimit, numbuf[2], flags & FL_SHORTNUMS);
558                         fprintf(fp, " %7s %5s %5s %6s\n\n", numbuf[0], numbuf[1], numbuf[2], timebuf);
559                 }
560         }
561
562
563         if (offender->offender_type == USRQUOTA)
564                 if (config->user_signature)
565                         format_print(fp, config->user_signature, offender->offender_name);
566                 else
567                         fprintf(fp, DEF_USER_SIGNATURE, config->support, config->phone);
568         else
569                 if (config->group_signature)
570                         format_print(fp, config->group_signature, offender->offender_name);
571                 else
572                         fprintf(fp, DEF_GROUP_SIGNATURE, config->support, config->phone);
573         fclose(fp);
574         if (wait(&status) < 0)  /* Wait for mailer */
575                 errstr(_("Cannot wait for mailer: %s\n"), strerror(errno));
576         else if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
577                 errstr(_("Warning: Mailer exitted abnormally.\n"));
578
579         return 0;
580 }
581
582 static int mail_to_offenders(struct configparams *config)
583 {
584         struct offenderlist *lptr;
585         int ret = 0;
586
587         /*
588          * Dump offenderlist.
589          */
590         for (lptr = offenders; lptr; lptr = lptr->next)
591                 ret |= mail_user(lptr, config);
592         return ret;
593 }
594
595 /*
596  * Wipe spaces, tabs, quotes and newlines from beginning and end of string
597  */
598 static void stripstring(char **buff)
599 {
600         int i;
601
602         /* first put a \0 at the tight place to end the string */
603         for (i = strlen(*buff) - 1; i >= 0 && (isspace((*buff)[i]) || (*buff)[i] == '"'
604              || (*buff)[i] == '\''); i--);
605         (*buff)[i+1] = 0;
606
607         /* then determine the position to start */
608         for (i = 0; (*buff)[i] && (isspace((*buff)[i]) || (*buff)[i] == '"' || (*buff)[i] == '\''); i++);
609         *buff += i;
610 }
611
612 /*
613  * Substitute '|' with end of lines
614  */
615 static void create_eoln(char *buf)
616 {
617         char *colpos = buf;
618
619         while ((colpos = strchr(colpos, '|')))
620                 *colpos = '\n';
621 }
622
623 /*
624  * Read /etc/quotatab (description of devices for users)
625  */
626 static int get_quotatable(void)
627 {
628         FILE *fp;
629         char buffer[IOBUF_SIZE], *colpos, *devname, *devdesc;
630         int line;
631         struct stat st;
632
633         if (!(fp = fopen(quotatabfile, "r"))) {
634                 errstr(_("Cannot open %s: %s\nWill use device names.\n"), quotatabfile, strerror(errno));
635                 qtab_i = 0;
636                 return 0;
637         }
638
639         line = 0;
640         for (qtab_i = 0; quotatable = srealloc(quotatable, sizeof(quotatable_t) * (qtab_i + 1)),
641              fgets(buffer, sizeof(buffer), fp); qtab_i++) {
642                 line++;
643                 quotatable[qtab_i].devname = NULL;
644                 quotatable[qtab_i].devdesc = NULL;
645                 if (buffer[0] == '#' || buffer[0] == ';') {     /* Comment? */
646                         qtab_i--;
647                         continue;
648                 }
649                 /* Empty line? */
650                 for (colpos = buffer; isspace(*colpos); colpos++);
651                 if (!*colpos) {
652                         qtab_i--;
653                         continue;
654                 }
655                 /* Parse line */
656                 if (!(colpos = strchr(buffer, ':'))) {
657                         errstr(_("Cannot parse line %d in quotatab (missing ':')\n"), line);
658                         qtab_i--;
659                         continue;
660                 }
661                 *colpos = 0;
662                 devname = buffer;
663                 devdesc = colpos+1;
664                 stripstring(&devname);
665                 stripstring(&devdesc);
666                 quotatable[qtab_i].devname = sstrdup(devname);
667                 quotatable[qtab_i].devdesc = sstrdup(devdesc);
668                 create_eoln(quotatable[qtab_i].devdesc);
669
670                 if (stat(quotatable[qtab_i].devname, &st) < 0)
671                         errstr(_("Cannot stat device %s (maybe typo in quotatab)\n"), quotatable[qtab_i].devname);
672         }
673         fclose(fp);
674         return 0;
675 }
676
677 /* Check correctness of the given format */
678 static void verify_format(char *fmt, char *varname)
679 {
680         char *ch;
681
682         for (ch = strchr(fmt, '%'); ch; ch = strchr(ch+2, '%')) {
683                 switch (*(ch+1)) {
684                         case 's':
685                         case 'i':
686                         case 'h':
687                         case 'd':
688                         case '%':
689                                 continue;
690                         default:
691                                 die(1, _("Incorrect format string for variable %s.\n\
692 Unrecognized expression %%%c.\n"), varname, *(ch+1));
693                 }
694         }
695 }
696
697 /*
698  * Reads config parameters from configfile
699  * uses default values if errstr occurs
700  */
701 static int readconfigfile(const char *filename, struct configparams *config)
702 {
703         FILE *fp;
704         char buff[IOBUF_SIZE];
705         char *var;
706         char *value;
707         char *pos;
708         int line, len, bufpos;
709
710         /* set default values */
711         sstrncpy(config->mail_cmd, MAIL_CMD, CNF_BUFFER);
712         sstrncpy(config->from, FROM, CNF_BUFFER);
713         sstrncpy(config->subject, SUBJECT, CNF_BUFFER);
714         sstrncpy(config->cc_to, CC_TO, CNF_BUFFER);
715         sstrncpy(config->support, SUPPORT, CNF_BUFFER);
716         sstrncpy(config->phone, PHONE, CNF_BUFFER);
717         (config->charset)[0] = '\0';
718         maildev[0] = 0;
719         config->user_signature = config->user_message = config->group_signature = config->group_message = NULL;
720         config->use_ldap_mail = 0;
721         config->cc_before = -1;
722
723 #ifdef USE_LDAP_MAIL_LOOKUP
724         config->ldap_port = config->ldap_is_setup = 0;
725         config->ldap_host[0] = 0;
726         config->ldap_uri[0] = 0;
727 #endif
728
729         if (!(fp = fopen(filename, "r"))) {
730                 errstr(_("Cannot open %s: %s\n"), filename, strerror(errno));
731                 return -1;
732         }
733
734         line = 0;
735         bufpos = 0;
736         while (fgets(buff + bufpos, sizeof(buff) - bufpos, fp)) {       /* start reading lines */
737                 line++;
738
739                 if (!bufpos) {
740                         /* check for comments or empty lines */
741                         if (buff[0] == '#' || buff[0] == ';')
742                                 continue;
743                         /* Is line empty? */
744                         for (pos = buff; isspace(*pos); pos++);
745                         if (!*pos)                      /* Nothing else was on the line */
746                                 continue;
747                 }
748                 len = bufpos + strlen(buff+bufpos);
749                 if (buff[len-1] != '\n')
750                         errstr(_("Line %d too long. Truncating.\n"), line);
751                 else {
752                         len--;
753                         if (buff[len-1] == '\\') {      /* Should join with next line? */
754                                 bufpos = len-1;
755                                 continue;
756                         }
757                 }
758                 buff[len] = 0;
759                 bufpos = 0;
760                 
761                 /* check for a '=' char */
762                 if ((pos = strchr(buff, '='))) {
763                         *pos = 0;       /* split buff in two parts: var and value */
764                         var = buff;
765                         value = pos + 1;
766
767                         stripstring(&var);
768                         stripstring(&value);
769
770                         /* check if var matches anything */
771                         if (!strcmp(var, "MAIL_CMD"))
772                                 sstrncpy(config->mail_cmd, value, CNF_BUFFER);
773                         else if (!strcmp(var, "FROM"))
774                                 sstrncpy(config->from, value, CNF_BUFFER);
775                         else if (!strcmp(var, "SUBJECT"))
776                                 sstrncpy(config->subject, value, CNF_BUFFER);
777                         else if (!strcmp(var, "CC_TO"))
778                                 sstrncpy(config->cc_to, value, CNF_BUFFER);
779                         else if (!strcmp(var, "SUPPORT"))
780                                 sstrncpy(config->support, value, CNF_BUFFER);
781                         else if (!strcmp(var, "PHONE"))
782                                 sstrncpy(config->phone, value, CNF_BUFFER);
783                         else if (!strcmp(var, "CHARSET"))
784                                 sstrncpy(config->charset, value, CNF_BUFFER);
785                         else if (!strcmp(var, "MAILDEV"))
786                                 /* set the global */
787                                 sstrncpy(maildev, value, CNF_BUFFER);
788                         else if (!strcmp(var, "MESSAGE")) {
789                                 config->user_message = sstrdup(value);
790                                 create_eoln(config->user_message);
791                                 verify_format(config->user_message, "MESSAGE");
792                         }
793                         else if (!strcmp(var, "SIGNATURE")) {
794                                 config->user_signature = sstrdup(value);
795                                 create_eoln(config->user_signature);
796                                 verify_format(config->user_signature, "SIGNATURE");
797                         }
798                         else if (!strcmp(var, "GROUP_MESSAGE")) {
799                                 config->group_message = sstrdup(value);
800                                 create_eoln(config->group_message);
801                                 verify_format(config->group_message, "GROUP_MESSAGE");
802                         }
803                         else if (!strcmp(var, "GROUP_SIGNATURE")) {
804                                 config->group_signature = sstrdup(value);
805                                 create_eoln(config->group_signature);
806                                 verify_format(config->group_signature, "GROUP_SIGNATURE");
807                         }
808                         else if (!strcmp(var, "LDAP_MAIL")) {
809                                 if(strcasecmp(value, "true") == 0) 
810                                         config->use_ldap_mail = 1;
811                                 else
812                                         config->use_ldap_mail = 0;
813                         }
814                         else if (!strcmp(var, "CC_BEFORE")) {
815                                 int num;
816                                 char unit[10];
817
818                                 if (sscanf(value, "%d%s", &num, unit) != 2)
819                                         goto cc_parse_err;
820                                 if (str2timeunits(num, unit, &config->cc_before) < 0) {
821 cc_parse_err:
822                                         die(1, _("Cannot parse time at CC_BEFORE variable (line %d).\n"), line);
823                                 }
824                         }
825 #ifdef USE_LDAP_MAIL_LOOKUP
826                         else if (!strcmp(var, "LDAP_HOST"))
827                                 sstrncpy(config->ldap_host, value, CNF_BUFFER);
828                         else if (!strcmp(var, "LDAP_PORT"))
829                                 config->ldap_port = (int)strtol(value, NULL, 10);
830                         else if (!strcmp(var, "LDAP_URI"))
831                                 sstrncpy(config->ldap_uri, value, CNF_BUFFER);
832                         else if(!strcmp(var, "LDAP_BINDDN"))
833                                 sstrncpy(config->ldap_binddn, value, CNF_BUFFER);
834                         else if(!strcmp(var, "LDAP_BINDPW"))
835                                 sstrncpy(config->ldap_bindpw, value, CNF_BUFFER);
836                         else if(!strcmp(var, "LDAP_BASEDN"))
837                                 sstrncpy(config->ldap_basedn, value, CNF_BUFFER);
838                         else if(!strcmp(var, "LDAP_SEARCH_ATTRIBUTE"))
839                                 sstrncpy(config->ldap_search_attr, value, CNF_BUFFER);
840                         else if(!strcmp(var, "LDAP_MAIL_ATTRIBUTE"))
841                                 sstrncpy(config->ldap_mail_attr, value, CNF_BUFFER);
842                         else if(!strcmp(var, "LDAP_DEFAULT_MAIL_DOMAIN"))
843                                 sstrncpy(config->default_domain, value, CNF_BUFFER);
844 #endif
845                         else    /* not matched at all */
846                                 errstr(_("Error in config file (line %d), ignoring\n"), line);
847                 }
848                 else            /* no '=' char in this line */
849                         errstr(_("Possible error in config file (line %d), ignoring\n"), line);
850         }
851         if (bufpos)
852                 errstr(_("Unterminated last line, ignoring\n"));
853 #ifdef USE_LDAP_MAIL_LOOKUP
854         if (config->use_ldap_mail)
855         {
856 #ifdef USE_LDAP_23
857                 if (!config->ldap_uri[0]) {
858                         snprintf(config->ldap_uri, CNF_BUFFER, "ldap://%s:%d", config->ldap_host, config->ldap_port);
859                         errstr(_("LDAP library version >= 2.3 detected. Please use LDAP_URI instead of hostname and port.\nGenerated URI %s\n"), config->ldap_uri);
860                 }
861 #else
862                 if (config->ldap_uri[0])
863                         die(1, _("LDAP library does not support ldap_initialize() but URI is specified."));
864 #endif
865         }
866 #endif
867         fclose(fp);
868
869         return 0;
870 }
871
872 static int admin_cmp(const void *a1, const void *a2)
873 {
874         return strcmp(((struct adminstable *)a1)->grpname, ((struct adminstable *)a2)->grpname);
875 }
876
877 /* Get administrators of the groups */
878 static int get_groupadmins(void)
879 {
880         FILE *f;
881         int line = 0;
882         char buffer[IOBUF_SIZE], *colpos, *grouppos, *endname, *adminpos;
883
884         if (!(f = fopen(adminsfile, "r"))) {
885                 errstr(_("Cannot open file with group administrators: %s\n"), strerror(errno));
886                 return -1;
887         }
888         
889         while (fgets(buffer, IOBUF_SIZE, f)) {
890                 line++;
891                 if (buffer[0] == ';' || buffer[0] == '#')
892                         continue;
893                 /* Skip initial spaces */
894                 for (colpos = buffer; isspace(*colpos); colpos++);
895                 if (!*colpos)   /* Empty line? */
896                         continue;
897                 /* Find splitting colon */
898                 for (grouppos = colpos; *colpos && *colpos != ':'; colpos++);
899                 if (!*colpos || grouppos == colpos) {
900                         errstr(_("Parse error at line %d. Cannot find end of group name.\n"), line);
901                         continue;
902                 }
903                 /* Cut trailing spaces */
904                 for (endname = colpos-1; isspace(*endname); endname--);
905                 *(++endname) = 0;
906                 /* Skip initial spaces at admins name */
907                 for (colpos++; isspace(*colpos); colpos++);
908                 if (!*colpos) {
909                         errstr(_("Parse error at line %d. Cannot find administrators name.\n"), line);
910                         continue;
911                 }
912                 /* Go through admins name */
913                 for (adminpos = colpos; !isspace(*colpos); colpos++);
914                 if (*colpos) {  /* Some characters after name? */
915                         *colpos = 0;
916                         /* Skip trailing spaces */
917                         for (colpos++; isspace(*colpos); colpos++);
918                         if (*colpos) {
919                                 errstr(_("Parse error at line %d. Trailing characters after administrators name.\n"), line);
920                                 continue;
921                         }
922                 }
923                 if (adminscnt >= adminsalloc)
924                         adminstable = srealloc(adminstable, sizeof(struct adminstable)*(adminsalloc+=ADMIN_TAB_ALLOC));
925                 adminstable[adminscnt].grpname = sstrdup(grouppos);
926                 adminstable[adminscnt++].adminname = sstrdup(adminpos);
927         }
928
929         fclose(f);
930         qsort(adminstable, adminscnt, sizeof(struct adminstable), admin_cmp);
931         return 0;
932 }
933
934 static struct quota_handle *find_handle_dev(char *dev, struct quota_handle **handles)
935 {
936         int i;
937
938         for (i = 0; handles[i] && strcmp(dev, handles[i]->qh_quotadev); i++);
939         return handles[i];
940 }
941
942 static void warn_quota(int fs_count, char **fs)
943 {
944         struct quota_handle **handles;
945         struct configparams config;
946         int i;
947
948         if (readconfigfile(configfile, &config) < 0)
949                 wc_exit(1);
950         if (get_quotatable() < 0)
951                 wc_exit(1);
952
953         if (flags & FL_USER) {
954                 handles = create_handle_list(fs_count, fs, USRQUOTA, -1, IOI_READONLY | IOI_INITSCAN, MS_LOCALONLY | (flags & FL_NOAUTOFS ? MS_NO_AUTOFS : 0));
955                 if (!maildev[0] || !strcasecmp(maildev, "any"))
956                         maildev_handle = NULL;
957                 else
958                         maildev_handle = find_handle_dev(maildev, handles);
959                 for (i = 0; handles[i]; i++)
960                         handles[i]->qh_ops->scan_dquots(handles[i], check_offence);
961                 dispose_handle_list(handles);
962         }
963         if (flags & FL_GROUP) {
964                 if (get_groupadmins() < 0)
965                         wc_exit(1);
966                 handles = create_handle_list(fs_count, fs, GRPQUOTA, -1, IOI_READONLY | IOI_INITSCAN, MS_LOCALONLY | (flags & FL_NOAUTOFS ? MS_NO_AUTOFS : 0));
967                 if (!maildev[0] || !strcasecmp(maildev, "any"))
968                         maildev_handle = NULL;
969                 else
970                         maildev_handle = find_handle_dev(maildev, handles);
971                 for (i = 0; handles[i]; i++)
972                         handles[i]->qh_ops->scan_dquots(handles[i], check_offence);
973                 dispose_handle_list(handles);
974         }
975         if (mail_to_offenders(&config) < 0)
976                 wc_exit(1);
977 }
978
979 /* Print usage information */
980 static void usage(void)
981 {
982         errstr(_("Usage:\n  warnquota [-ugsid] [-F quotaformat] [-c configfile] [-q quotatabfile] [-a adminsfile] [filesystem...]\n\n\
983 -u, --user                      warn users\n\
984 -g, --group                     warn groups\n\
985 -s, --human-readable            send information in more human friendly units\n\
986 -i, --no-autofs                 avoid autofs mountpoints\n\
987 -d, --no-details                do not send quota information itself\n\
988 -F, --format=formatname         use quotafiles of specific format\n\
989 -c, --config=config-file        non-default config file\n\
990 -q, --quota-tab=quotatab-file   non-default quotatab\n\
991 -a, --admins-file=admins-file   non-default admins file\n\
992 -h, --help                      display this help message and exit\n\
993 -v, --version                   display version information and exit\n\n"));
994         fprintf(stderr, _("Bugs to %s\n"), MY_EMAIL);
995         wc_exit(1);
996 }
997  
998 static void parse_options(int argcnt, char **argstr)
999 {
1000         int ret;
1001         struct option long_opts[] = {
1002                 { "user", 0, NULL, 'u' },
1003                 { "group", 0, NULL, 'g' },
1004                 { "version", 0, NULL, 'V' },
1005                 { "help", 0, NULL, 'h' },
1006                 { "format", 1, NULL, 'F' },
1007                 { "config", 1, NULL, 'c' },
1008                 { "quota-tab", 1, NULL, 'q' },
1009                 { "admins-file", 1, NULL, 'a' },
1010                 { "no-autofs", 0, NULL, 'i' },
1011                 { "human-readable", 0, NULL, 's' },
1012                 { "no-details", 0, NULL, 'd' },
1013                 { NULL, 0, NULL, 0 }
1014         };
1015  
1016         while ((ret = getopt_long(argcnt, argstr, "ugVF:hc:q:a:isd", long_opts, NULL)) != -1) {
1017                 switch (ret) {
1018                   case '?':
1019                   case 'h':
1020                         usage();
1021                   case 'V':
1022                         version();
1023                         exit(0);
1024                   case 'F':
1025                         if ((fmt = name2fmt(optarg)) == QF_ERROR)
1026                                 wc_exit(1);
1027                         break;
1028                   case 'c':
1029                         configfile = optarg;
1030                         break;
1031                   case 'q':
1032                         quotatabfile = optarg;
1033                         break;
1034                   case 'a':
1035                         adminsfile = optarg;
1036                         break;
1037                   case 'u':
1038                         flags |= FL_USER;
1039                         break;
1040                   case 'g':
1041                         flags |= FL_GROUP;
1042                         break;
1043                   case 'i':
1044                         flags |= FL_NOAUTOFS;
1045                         break;
1046                   case 's':
1047                         flags |= FL_SHORTNUMS;
1048                         break;
1049                   case 'd':
1050                         flags |= FL_NODETAILS;
1051                         break;
1052                 }
1053         }
1054         if (!(flags & FL_USER) && !(flags & FL_GROUP))
1055                 flags |= FL_USER;
1056 }
1057  
1058 static void get_host_name(void)
1059 {
1060         struct utsname uts;
1061
1062         if (uname(&uts))
1063                 die(1, _("Cannot get host name: %s\n"), strerror(errno));
1064         hostname = sstrdup(uts.nodename);
1065         domainname = sstrdup(uts.domainname);
1066 }
1067
1068 int main(int argc, char **argv)
1069 {
1070         gettexton();
1071         progname = basename(argv[0]);
1072         get_host_name();
1073
1074         parse_options(argc, argv);
1075         init_kernel_interface();
1076         warn_quota(argc - optind, argc > optind ? argv + optind : NULL);
1077
1078         wc_exit(0);
1079         return 0;
1080 }