1 /*
2  * $Id: qmailadmin.c,v 1.6.2.16 2009/05/02 19:13:29 tomcollins Exp $
3  * Copyright (C) 1999-2004 Inter7 Internet Technologies, Inc.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18  */
19 
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24 #include <sys/stat.h>
25 #include <unistd.h>
26 #include <pwd.h>
27 #include <dirent.h>
28 #include <vpopmail_config.h>
29 /* undef some macros that get redefined in config.h below */
30 #undef PACKAGE_NAME
31 #undef PACKAGE_STRING
32 #undef PACKAGE_TARNAME
33 #undef PACKAGE_VERSION
34 
35 #include <vpopmail.h>
36 #include <vauth.h>
37 #include <vlimits.h>
38 
39 #include "alias.h"
40 #include "auth.h"
41 #include "autorespond.h"
42 #include "cgi.h"
43 #include "command.h"
44 #include "config.h"
45 #include "limits.h"
46 #include "mailinglist.h"
47 #include "printh.h"
48 #include "qmailadmin.h"
49 #include "show.h"
50 #include "template.h"
51 #include "user.h"
52 #include "util.h"
53 
54 char Username[MAX_BUFF];
55 char Domain[MAX_BUFF];
56 char Password[MAX_BUFF];
57 char Gecos[MAX_BUFF];
58 char Quota[MAX_BUFF];
59 char Time[MAX_BUFF];
60 char ActionUser[MAX_BUFF];
61 char Newu[MAX_BUFF];
62 char Password1[MAX_BUFF];
63 char Password2[MAX_BUFF];
64 char Crypted[MAX_BUFF];
65 char Alias[MAX_BUFF];
66 char LineData[MAX_BUFF];
67 char Action[MAX_BUFF];
68 char Message[MAX_BIG_BUFF];
69 char StatusMessage[MAX_BIG_BUFF];
70 int num_of_mailinglist;
71 int CGIValues[256];
72 char Pagenumber[MAX_BUFF];
73 char SearchUser[MAX_BUFF];
74 time_t Mytime;
75 char *TmpCGI = NULL;
76 char TmpBuf[MAX_BIG_BUFF];
77 char TmpBuf1[MAX_BUFF];
78 char TmpBuf2[MAX_BUFF];
79 char TmpBuf3[MAX_BUFF];
80 char TempBuf[MAX_BUFF];
81 int Compressed;
82 FILE *actout;
83 FILE *lang_fs;
84 FILE *color_table;
85 char *html_text[MAX_LANG_STR+1];
86 
87 struct vlimits Limits;
88 int AdminType;
89 int MaxPopAccounts;
90 int MaxAliases;
91 int MaxForwards;
92 int MaxAutoResponders;
93 int MaxMailingLists;
94 
95 int DisablePOP;
96 int DisableIMAP;
97 int DisableDialup;
98 int DisablePasswordChanging;
99 int DisableWebmail;
100 int DisableRelay;
101 
102 int CurPopAccounts;
103 int CurForwards;
104 int CurBlackholes;
105 int CurAutoResponders;
106 int CurMailingLists;
107 uid_t Uid;
108 gid_t Gid;
109 char RealDir[156];
110 char Lang[40];
111 
qmailadmin_suid(gid_t Gid,uid_t Uid)112 void qmailadmin_suid (gid_t Gid, uid_t Uid)
113 {
114   if ( geteuid() == 0 ) {
115     if ( setgid(Gid) != 0 ) {
116       printf ("%s", html_text[318]);
117       perror("setgid");
118       vclose();
119       exit (EXIT_FAILURE);
120     }
121     if ( setuid(Uid) != 0 ) {
122       printf ("%s", html_text[319]);
123       perror("setuid");
124       vclose();
125       exit (EXIT_FAILURE);
126     }
127   }
128 }
129 
main(argc,argv)130 int main(argc,argv)
131  int argc;
132  char *argv[];
133 {
134  const char *ip_addr=getenv("REMOTE_ADDR");
135  const char *x_forward=getenv("HTTP_X_FORWARDED_FOR");
136  char *pi;
137  char *rm;
138  char returnhttp[MAX_BUFF];
139  char returntext[MAX_BUFF];
140  struct vqpasswd *pw;
141 
142   init_globals();
143 
144   if (x_forward) ip_addr = x_forward;
145   if (!ip_addr) ip_addr = "127.0.0.1";
146   pi=getenv("PATH_INFO");
147   if ( pi )  pi = strdup(pi);
148 
149   if (pi) snprintf (TmpBuf2, sizeof(TmpBuf2), "%s", pi + 5);
150   rm = getenv("REQUEST_METHOD");
151   rm = (rm == NULL ? "" : strdup(rm));
152 
153   if ( strncmp(rm , "POST", 4) == 0 ) {
154     get_cgi();
155   } else {
156     TmpCGI = getenv("QUERY_STRING");
157     TmpCGI = (TmpCGI == NULL ? "" : strdup(TmpCGI));
158   }
159 
160   if (pi && strncmp(pi, "/com/", 5) == 0) {
161     GetValue(TmpCGI, Username, "user=", sizeof(Username));
162     GetValue(TmpCGI, Domain, "dom=", sizeof(Domain));
163     GetValue(TmpCGI, Time, "time=", sizeof(Time));
164     Mytime = atoi(Time);
165     pw = vauth_getpw( Username, Domain );
166 
167     /* get the real uid and gid and change to that user */
168     vget_assign(Domain,RealDir,sizeof(RealDir),&Uid,&Gid);
169     qmailadmin_suid (Gid, Uid);
170 
171     if ( chdir(RealDir) < 0 ) {
172       fprintf(stderr, "<h2>%s %s</h2>\n", html_text[171], RealDir );
173     }
174     load_limits();
175 
176     set_admin_type();
177 
178     if ( AdminType == USER_ADMIN || AdminType == DOMAIN_ADMIN ) {
179       auth_user_domain(ip_addr, pw);
180     } else {
181       auth_system(ip_addr, pw);
182     }
183 
184     process_commands();
185 
186   } else if (pi && strncmp(pi, "/passwd/", 7) == 0) {
187     char User[MAX_BUFF];
188     char *dom;
189 
190     GetValue(TmpCGI, Username, "address=", sizeof(Username));
191     GetValue(TmpCGI, Password, "oldpass=", sizeof(Password));
192     GetValue(TmpCGI, Password1, "newpass1=", sizeof(Password1));
193     GetValue(TmpCGI, Password2, "newpass2=", sizeof(Password2));
194 
195     if (*Username && (*Password == '\0') && (*Password1 || *Password2)) {
196       /* username entered, but no password */
197       snprintf (StatusMessage, sizeof(StatusMessage), "%s", html_text[198]);
198     } else if (*Username && *Password) {
199       /* attempt to authenticate user */
200       vget_assign (Domain, RealDir, sizeof(RealDir), &Uid, &Gid);
201       qmailadmin_suid (Gid, Uid);
202 
203       strcpy (User, Username);
204       if ((dom = strchr (User, '@')) != NULL) {
205         strcpy (Domain, dom+1);
206         *dom = '\0';
207       }
208 
209       if ( *Domain == '\0' ) {
210         snprintf (StatusMessage, sizeof(StatusMessage), "%s", html_text[198]);
211       } else {
212         chdir(RealDir);
213         load_limits();
214 
215         pw = vauth_user( User, Domain, Password, "" );
216         if ( pw == NULL ) {
217           snprintf (StatusMessage, sizeof(StatusMessage), "%s", html_text[198]);
218         } else if (pw->pw_flags & NO_PASSWD_CHNG) {
219           strcpy (StatusMessage, "You don't have permission to change your password.");
220         } else if (strcmp (Password1, Password2) != 0) {
221           snprintf (StatusMessage, sizeof(StatusMessage), "%s", html_text[200]);
222         } else if (*Password1 == '\0') {
223           snprintf (StatusMessage, sizeof(StatusMessage), "%s", html_text[234]);
224         } else if (vpasswd (User, Domain, Password1, USE_POP) != VA_SUCCESS) {
225           snprintf (StatusMessage, sizeof(StatusMessage), "%s", html_text[140]);
226 #ifndef TRIVIAL_PASSWORD_ENABLED
227          } else if ( strstr(User,Password1)!=NULL) {
228           snprintf (StatusMessage, sizeof(StatusMessage), "%s\n", html_text[320]);
229 #endif
230         } else {
231           /* success */
232           snprintf (StatusMessage, sizeof(StatusMessage), "%s", html_text[139]);
233           *Password = '\0';
234           send_template ("change_password_success.html");
235 
236           return 0;
237         }
238       }
239     }
240 
241     send_template ("change_password.html");
242     return 0;
243 
244   } else if (*rm) {
245    FILE *fs;
246    char *dom;
247 
248        GetValue(TmpCGI, Username, "username=", sizeof(Username));
249        GetValue(TmpCGI, Domain, "domain=", sizeof(Domain));
250        GetValue(TmpCGI, Password, "password=", sizeof(Password));
251        if ((dom = strchr (Username, '@')) != NULL) {
252          strcpy (Domain, dom+1);
253          *dom = '\0';
254        }
255 
256        vget_assign(Domain,RealDir,sizeof(RealDir),&Uid,&Gid);
257        qmailadmin_suid (Gid, Uid);
258 
259        /* Authenticate a user and domain admin */
260        if ( strlen(Domain) > 0 ) {
261          chdir(RealDir);
262          load_limits();
263 
264          pw = vauth_user( Username, Domain, Password, "" );
265          if ( pw == NULL ) {
266            snprintf (StatusMessage, sizeof(StatusMessage), "%s\n", html_text[198]);
267            show_login();
268            vclose();
269            exit(0);
270          }
271 
272          snprintf (TmpBuf, sizeof(TmpBuf), "%s/" MAILDIR, pw->pw_dir);
273          del_id_files( TmpBuf);
274 
275          Mytime = time(NULL);
276          snprintf (TmpBuf, sizeof(TmpBuf), "%s/" MAILDIR "/%u.qw", pw->pw_dir, (unsigned int) Mytime);
277          fs = fopen(TmpBuf, "w");
278          if ( fs == NULL ) {
279            printf ("%s %s<br>\n", html_text[144], TmpBuf);
280            vclose();
281            exit(0);
282          }
283          memset(TmpBuf, 0, sizeof(TmpBuf));
284          /* set session vars */
285          GetValue(TmpCGI, returntext, "returntext=", sizeof(returntext));
286          GetValue(TmpCGI, returnhttp, "returnhttp=", sizeof(returnhttp));
287          snprinth (TmpBuf, sizeof(TmpBuf), "ip_addr=%C&returntext=%C&returnhttp=%C\n",
288                  ip_addr, returntext, returnhttp);
289          fputs(TmpBuf,fs);
290         // fputs(ip_addr, fs);
291          fclose(fs);
292          vget_assign(Domain, TmpBuf1, sizeof(TmpBuf1), &Uid, &Gid);
293          set_admin_type();
294 
295          /* show the main menu for domain admins, modify user page
296             for regular users */
297          if (AdminType == DOMAIN_ADMIN) {
298            show_menu(Username, Domain, Mytime);
299          } else {
300            strcpy (ActionUser, Username);
301            moduser();
302          }
303          vclose();
304          exit(0);
305        }
306   }
307   show_login();
308   vclose();
309 
310   return 0;
311 }
312 
load_lang(char * lang)313 void load_lang (char *lang)
314 {
315   long lang_size;
316   size_t bytes_read;
317   char *lang_entries;
318   char *id;
319   char *p;
320 
321   if (open_lang( lang))
322   {
323     // Rare error likely caused by improper installation, should probably be
324     // handled by regular error system, but this is a quick band-aid.
325     printf("Content-Type: text/html\r\n\r\n");
326     printf("<html> <head>\r\n");
327     printf("<title>Failed to open lang file:%s</title>\r\n",lang);
328     printf("</head>\r\n<body>\r\n");
329     printf("<h1>qmailadmin error</h1>\r\n");
330     printf("<p>Failed to open lang file: %s. Please check your lang directory.\r\n", lang);
331     printf("</body></html>\r\n");
332     exit(-1);
333   }
334 
335   fseek (lang_fs, 0, SEEK_END);
336   lang_size = ftell (lang_fs);
337   lang_entries = malloc (lang_size);
338 
339   if (lang_entries == NULL) return;
340   rewind (lang_fs);
341 
342   bytes_read = fread (lang_entries, 1, lang_size, lang_fs);
343   /* error handling for incomplete reads? */
344 
345   id = strtok (lang_entries, " \t");
346   while (id) {
347     p = strtok (NULL, "\n");
348     if (p == NULL) break;
349     html_text[atoi(id)] = p;
350     id = strtok (NULL, " \t");
351   }
352 
353   /* Do not free lang_entries!  html_text points into it! */
354 }
355 
init_globals()356 void init_globals()
357 {
358   char *accept_lang;
359   char *langptr, *qptr;
360   char *charset;
361   int lang_err;
362   int i;
363   float maxq, thisq;
364 
365   memset(CGIValues, 0, sizeof(CGIValues));
366   CGIValues['0'] = 0;
367   CGIValues['1'] = 1;
368   CGIValues['2'] = 2;
369   CGIValues['3'] = 3;
370   CGIValues['4'] = 4;
371   CGIValues['5'] = 5;
372   CGIValues['6'] = 6;
373   CGIValues['7'] = 7;
374   CGIValues['8'] = 8;
375   CGIValues['9'] = 9;
376   CGIValues['A'] = 10;
377   CGIValues['B'] = 11;
378   CGIValues['C'] = 12;
379   CGIValues['D'] = 13;
380   CGIValues['E'] = 14;
381   CGIValues['F'] = 15;
382   CGIValues['a'] = 10;
383   CGIValues['b'] = 11;
384   CGIValues['c'] = 12;
385   CGIValues['d'] = 13;
386   CGIValues['e'] = 14;
387   CGIValues['f'] = 15;
388 
389   actout = stdout;
390   memset(Username, 0, sizeof(Username));
391   memset(Domain, 0, sizeof(Domain));
392   memset(Password, 0, sizeof(Password));
393   memset(Quota, 0, sizeof(Quota));
394   memset(Time, 0, sizeof(Time));
395   memset(ActionUser, 0, sizeof(ActionUser));
396   memset(Newu, 0, sizeof(Newu));
397   memset(Password1, 0, sizeof(Password1));
398   memset(Password2, 0, sizeof(Password2));
399   memset(Crypted, 0, sizeof(Crypted));
400   memset(Alias, 0, sizeof(Alias));
401   memset(Message, 0, sizeof(Message));
402   memset(TmpBuf, 0, sizeof(TmpBuf));
403   memset(TmpBuf1, 0, sizeof(TmpBuf1));
404   memset(TmpBuf2, 0, sizeof(TmpBuf2));
405   memset(TmpBuf3, 0, sizeof(TmpBuf3));
406 
407   AdminType = NO_ADMIN;
408 
409   lang_fs = NULL;
410 
411   /* Parse HTTP_ACCEPT_LANGUAGE to find highest preferred language
412    * that we have a translation for.  Example setting:
413    * de-de, ja;q=0.25, en;q=0.50, de;q=0.75
414    * The q= lines determine which is most preferred, defaults to 1.
415    * Our routine starts with en at 0.0, and then would try de-de (1.00),
416    * de (1.00), ja (0.25), en (0.50), and then de (0.75).
417    */
418 
419   /* default to English at 0.00 preference */
420   maxq = 0.0;
421   strcpy (Lang, "en");
422 
423   /* read in preferred languages */
424   langptr = getenv("HTTP_ACCEPT_LANGUAGE");
425   if (langptr != NULL) {
426     accept_lang = malloc (strlen(langptr));
427     strcpy (accept_lang, langptr);
428     langptr = strtok(accept_lang, " ,\n");
429     while (langptr != NULL) {
430       qptr = strstr (langptr, ";q=");
431       if (qptr != NULL) {
432         *qptr = '\0';  /* convert semicolon to NULL */
433         thisq = (float) atof (qptr+3);
434       } else {
435         thisq = 1.0;
436       }
437 
438       /* if this is a better match than our previous best, try it */
439       if (thisq > maxq) {
440         lang_err = open_lang (langptr);
441 
442         /* Remove this next section for strict interpretation of
443          * HTTP_ACCEPT_LANGUAGE.  It will try language xx (with the
444          * same q value) if xx-yy fails.
445          */
446         if ((lang_err == -1) && (langptr[2] == '-')) {
447           langptr[2] = '\0';
448           lang_err = open_lang (langptr);
449         }
450 
451         if (lang_err == 0) {
452           maxq = thisq;
453           strcpy (Lang, langptr);
454         }
455       }
456       langptr = strtok (NULL, " ,\n");
457     }
458 
459     free(accept_lang);
460   }
461 
462   /* best language choice is now in 'Lang' */
463 
464   /* build table of html_text entries */
465   for (i = 0; i <= MAX_LANG_STR; i++) html_text[i] = "";
466 
467   /* read English first as defaults for incomplete language files */
468   if (strcmp (Lang, "en") != 0) load_lang ("en");
469 
470   /* load the preferred language */
471   load_lang (Lang);
472 
473   /* open the color table */
474   open_colortable();
475 
476   umask(VPOPMAIL_UMASK);
477 
478   charset = html_text[0];
479   printf ("Content-Type: text/html; charset=%s\n",
480     *charset == '\0' ? "iso-8859-1" : charset);
481 #ifdef NO_CACHE
482   printf ("Cache-Control: no-cache\n");
483   printf ("Cache-Control: no-store\n");
484   printf ("Pragma: no-cache\n");
485   printf ("Expires: Thu, 01 Dec 1994 16:00:00 GMT\n");
486 #endif
487   printf ("\n");
488 }
489 
del_id_files(char * dirname)490 void del_id_files( char *dirname )
491 {
492  DIR *mydir;
493  struct dirent *mydirent;
494 
495   mydir = opendir(dirname);
496   if ( mydir == NULL ) return;
497 
498   while((mydirent=readdir(mydir))!=NULL){
499     if ( strstr(mydirent->d_name,".qw")!=0 ) {
500       snprintf (TmpBuf3, sizeof(TmpBuf3), "%s/%s", dirname, mydirent->d_name);
501       unlink(TmpBuf3);
502     }
503   }
504   closedir(mydir);
505 }
506 
quickAction(char * username,int action)507 void quickAction (char *username, int action)
508 {
509   /* This feature sponsored by PinkRoccade Public Sector, Sept 2004 */
510 
511   struct stat fileinfo;
512   char dotqmailfn[MAX_BUFF];
513   char *space, *ar, *ez;
514   char *aliasline;
515 
516   /* Note that all of the functions called from quickAction() assume
517    * that the username to modify is in a global called "ActionUser"
518    * It would be better to pass this information as a parameter, but
519    * that's how it was originally done.  The code in command.c that
520    * calls quickAction() passes ActionUser as the username parameter
521    * in hopes that someday we'll remove the globals and pass parameters.
522    */
523 
524   /* first check for alias/forward, autorepsonder (or even mailing list) */
525   aliasline = valias_select (username, Domain);
526   if (aliasline != NULL) {
527     /* Autoresponder/Mailing List detection algorithm:
528      * We're looking for either '/autorespond ' or '/ezmlm-reject ' to
529      * appear in the first line, before a space appears
530      */
531     space = strstr (aliasline, " ");
532     ar = strstr (aliasline, "/autorespond ");
533     ez = strstr (aliasline, "/ezmlm-reject ");
534     if (ar && space && (ar < space)) {
535       /* autorepsonder */
536       if (action == ACTION_MODIFY) modautorespond();
537       else if (action == ACTION_DELETE) delautorespond();
538     } else if (ez && space && (ez < space)) {
539       /* mailing list (cdb-backend only) */
540       if (action == ACTION_MODIFY) modmailinglist();
541       else if (action == ACTION_DELETE) delmailinglist();
542     } else {
543       /* it's just a forward/alias of some sort */
544       if (action == ACTION_MODIFY) moddotqmail();
545       else if (action == ACTION_DELETE) deldotqmail();
546     }
547   } else if (vauth_getpw (username, Domain)) {
548     /* POP/IMAP account */
549     if (action == ACTION_MODIFY) moduser();
550     else if (action == ACTION_DELETE) {
551       // don't allow deletion of postmaster account
552       if (strcasecmp (username, "postmaster") == 0) {
553         snprinth (StatusMessage, sizeof(StatusMessage), "%s", html_text[317]);
554         show_menu(Username, Domain, Mytime);
555         vclose();
556       } else deluser();
557     }
558   } else {
559     /* check for mailing list on SQL backend (not in valias_select) */
560     snprintf (dotqmailfn, sizeof(dotqmailfn), ".qmail-%s", username);
561     str_replace (dotqmailfn+7, '.', ':');
562     if (stat (dotqmailfn, &fileinfo) == 0) {
563       /* mailing list (MySQL backend) */
564       if (action == ACTION_MODIFY) modmailinglist();
565       else if (action == ACTION_DELETE) delmailinglist();
566     } else {
567       /* user does not exist */
568       snprinth (StatusMessage, sizeof(StatusMessage), "%s (%H@%H)",
569         html_text[153], username, Domain);
570       show_menu(Username, Domain, Mytime);
571       vclose();
572     }
573   }
574 }
575