/* #Specification: mail2fax / principle The mail2fax utility is intend as a mail delivery engine for sendmail. It delivers the mail to a fax spooling engine. It does various test to find out if the sender is allowed to fax. If any error, some strings are sent back to sendmail and non zero value is returned. This utility must manage MIME message and use various filters to translate those into something the fax spooling understand. */ #include #include #include #include #include #include #include #include #include #include #include "misc.h" #include "mailhead.h" #include "../paths.h" struct CONFPARM { char faxcmd[200]; char faxlog[200]; char faxlogcmd[200]; }; static void mail2fax_getconfparm (CONFPARM *cnf) { FILE *fin = fopen (ETC_CONF_LINUXCONF,"r"); strcpy (cnf->faxcmd,"/usr/bin/faxspool"); cnf->faxlog[0] = '\0'; cnf->faxlogcmd[0] = '\0'; if (fin != NULL){ char buf[1000]; while (fgets(buf,sizeof(buf)-1,fin)!=NULL){ char v1[1000],v2[1000]; v2[0] = '\0'; if (sscanf (buf,"%s %s",v1,v2) >= 1){ if (strcmp(v1,"mailfax.faxcmd")==0){ if (v2[0] != '\0') strcpy (cnf->faxcmd,v2); break; }else if (strcmp(v1,"mailfax.faxlog")==0){ if (v2[0] != '\0') strcpy (cnf->faxlog,v2); break; }else if (strcmp(v1,"mailfax.faxcmdlog")==0){ if (v2[0] != '\0') strcpy (cnf->faxlogcmd,v2); break; } } } fclose (fin); } } struct FAX_USER{ char email[100]; char name[100]; char pgp[1000]; }; struct FAX_ZONE{ char id[100]; int len; char prefix[100]; char rule[100]; }; struct MAILFAX_ACCESS{ char origin; // Access controled by the source of the email char user; // Type of user accepted char pgp; // PGP is required }; struct FAX_RULE{ char id[100]; MAILFAX_ACCESS acc; }; struct FAX_ALIAS{ char phone[100]; char name[100]; char rule[100]; }; struct FAX_DEST { char name[MAXHLINE+1]; char phone[MAXHLINE+1]; char rule[MAXHLINE+1]; char pathtmp[PATH_MAX]; }; static void mail2fax_trimphone(const char *phone, char *trimphone) { while (*phone != '\0'){ if (isdigit(*phone)) *trimphone++ = *phone; phone++; } *trimphone = 0; } /* We know the phone number. We look for a fax zone */ static int mail2fax_lookupzone ( MAILHEADER *head, const char *phone, FAX_ZONE *zone) { int ret = -1; FILE *fin = fopen (ETC_FAX_ZONES,"r"); zone->id[0] = zone->prefix[0] = zone->rule[0] = '\0'; if (fin != NULL){ char trimphone[100], buf[1000]; int maxprefix=-1; mail2fax_trimphone(phone,trimphone); while (fgets(buf,sizeof(buf)-1,fin)!=NULL){ char tb[4][100]; if (str_splitline(buf,':',tb,4) != -1){ char prefix[100]; int lenprefix; mail2fax_trimphone (tb[1],prefix); lenprefix = strlen(prefix); if (lenprefix > maxprefix && strncmp(trimphone,prefix,lenprefix)==0){ unsigned zone_len = atoi(tb[2]); if (zone_len != strlen(trimphone)){ // lenprefix == 0 means to accept anything // of a given zone_len. If the trimphone is too // long, it does not fit this zone. // If lenprefix != 0, then trimphone must match this // zone, or else this is an invalid phone number if (lenprefix != 0){ fprintf (stderr,"Invalid phone number %s\n",phone); syslog (LOG_ERR,"Invalid phone number %s from %s",phone,head->from.adr); break; } }else{ syslog (LOG_ERR,"Matching zone :%s: len %d",tb[1],maxprefix); zone->len = zone_len; maxprefix = lenprefix; strcpy (zone->rule,tb[3]); strcpy (zone->id,tb[0]); strcpy (zone->prefix,prefix); ret = 0; } } } } fclose (fin); } return ret; } /* Ok, we know that the fax may be sent. The user is identified. Now we have to check (if need) the PGP signature */ static int mail2fax_validsign ( FAX_RULE *info, FAX_USER *, // user, MAILHEADER *, // head, FAX_DEST *) // dest) { int ret = -1; if (info->acc.user == 0){ fprintf (stderr,"PGP authentification not done\n"); syslog (LOG_ERR,"Can't authenticate with pgp yet\n"); }else{ ret = 0; } return ret; } /* We know the phone number. We look for a fax zone and check if faxing is granted */ static int mail2fax_checkrule ( MAILHEADER *head, FAX_DEST *dest, FAX_USER *user) { int ret = -1; FILE *fin = fopen (ETC_FAX_RULES,"r"); if (fin != NULL){ char buf[300]; const char *rule = dest->rule; int lenrule = strlen(rule); FAX_RULE info; int user_match = 0; info.acc.origin = info.acc.pgp = info.acc.user = 100; while (fgets(buf,sizeof(buf)-1,fin)!=NULL){ char keyword[100],val[100]; if (strncmp(rule,buf,lenrule)==0 && buf[lenrule] == '.' && sscanf(buf,"%s %s",keyword,val)==2){ char *suffix = keyword+lenrule+1; if (strcmp(suffix,"accorigin")==0){ info.acc.origin = atoi(val); }else if (strcmp(suffix,"accuser")==0){ info.acc.user = atoi(val); }else if (strcmp(suffix,"accpgp")==0){ info.acc.pgp = atoi(val); }else if (strcmp(suffix,"user")==0){ if (strcmp(val,user->email)==0){ syslog (LOG_ERR,"User %s match rule %s",val,rule); user_match = 1; } } } } if (info.acc.origin == 3){ /* Fax from anywhere accepted */ ret = 0; }else if (info.acc.origin == 2){ if (head->nbreceived == 1){ ret = 0; }else{ fprintf (stderr,"Not autorised to fax to this destination\n"); syslog (LOG_ERR,"Remote access denied: %s -> %s",head->from.adr ,dest->phone); } }else if(info.acc.origin == 1){ if (user->email[0] != '\0'){ ret = mail2fax_validsign (&info,user,head,dest); }else{ fprintf (stderr,"Not autorised to fax to this destination\n"); syslog (LOG_ERR,"User access denied: %s -> %s",head->from.adr ,dest->phone); } }else if (info.acc.origin == 0){ /* Only zone users accepted */ if (user_match){ ret = mail2fax_validsign (&info,user,head,dest); }else{ fprintf (stderr,"Not autorised to fax to this destination\n"); syslog (LOG_ERR,"User access denied: %s -> %s",head->from.adr ,dest->phone); } }else{ fprintf (stderr,"Incomplete rule %s\n",rule); syslog (LOG_ERR,"Rule %s is corrupted",rule); } fclose (fin); }else{ fprintf (stderr,"Rule database empty, can't fax\n"); syslog (LOG_ERR,"Can't open %s(%m), can't fax",ETC_FAX_RULES); } return ret; } /* Locate an alias record in the fax aliases database */ static int mail2fax_lookupalias ( const char *alias, FAX_ALIAS *ali) { int ret = -1; FILE *fin = fopen (ETC_FAX_ALIASES,"r"); if (fin != NULL){ char buf[1000]; int len = strlen(alias); while (fgets(buf,sizeof(buf)-1,fin)!=NULL){ if (strncasecmp(buf,alias,len)==0 && buf[len] == ':'){ char tb[3][100]; if (str_splitline (buf+len+1,':',tb,3)!=-1){ strcpy (ali->name,tb[0]); strcpy (ali->phone,tb[1]); strcpy (ali->rule,tb[2]); ret = 0; }else{ fprintf (stderr,"malformed fax aliases database\n"); syslog (LOG_ERR,"/etc/fax/faxaliases is malformed for alias %s",alias); } break; } } fclose (fin); } syslog (LOG_ERR,"lookupuser %d :%s: :%s:",ret,ali->name,ali->phone); return ret; } /* Locate some information about a potential fax user */ static int mail2fax_lookupuser ( const char *email, FAX_USER *user) { int ret = -1; FILE *fin = fopen (ETC_FAX_USERS,"r"); if (fin != NULL){ char buf[1000]; int len = strlen(email); while (fgets(buf,sizeof(buf)-1,fin)!=NULL){ if (strncmp(buf,email,len)==0 && buf[len] == ':'){ char *pt = buf+len+1; char *end = strchr(pt,':'); if (end != NULL){ *end++ = '\0'; strcpy (user->email,email); strcpy (user->name,pt); strip_end (end); strcpy (user->pgp,end); ret = 0; }else{ fprintf (stderr,"malformed fax user database\n"); syslog (LOG_ERR,"/etc/fax/faxusers is malformed for user %s",email); } break; } } fclose (fin); } syslog (LOG_ERR,"lookupuser %d :%s: :%s: :%s:",ret,user->email,user->name,user->pgp); return ret; } /* Check if accces is ok to the FAX destination */ static int mail2fax_accessok( MAILHEADER *head, FAX_DEST *dest) { int ret = -1; FAX_USER user; user.email[0] = user.name[0] = user.pgp[0] = '\0'; mail2fax_lookupuser (head->from.adr,&user); if (head->from.name[0] == '\0' && user.name[0] != '\0'){ mail2fax_stripcopy (head->from.name,user.name); } if (!isdigit(*dest->phone)){ /* Ok, we must look in the alias database */ FAX_ALIAS alias; if (mail2fax_lookupalias(dest->phone,&alias)==-1){ fprintf (stderr,"Can't locate fax alias %s\n",dest->phone); }else{ strcpy (dest->name,alias.name); strcpy (dest->phone,alias.phone); strcpy (dest->rule,alias.rule); ret = mail2fax_checkrule (head,dest,&user); } }else{ /* Ok, we must locate the zone */ FAX_ZONE zone; if (mail2fax_lookupzone(head,dest->phone,&zone)==-1){ fprintf (stderr,"Not autorised to fax to this destination: No matching zone\n"); syslog (LOG_ERR,"No matching zone for fax number %s",dest->phone); }else{ strcpy (dest->rule,zone.rule); ret = mail2fax_checkrule (head,dest,&user); } } return ret; } /* Check if a string contain special shell character which may be used to do a security attack. */ static int mail2fax_checkshellchar (const char *str) { /* #Specification: mail2fax / rejecting special characters Special shell characters are checked in the phone, sender name, sender email, and destination name. If any special shell character is there, the fax is rejected. Here are the special character which are looked for # '`' '\'' '"' '(' ')' ';' '&' '|' '=' # */ int ret = 0; while (*str != '\0'){ char carac = *str++; if (carac == '*' || carac == '`' || carac == '\'' || carac == '"' || carac == '(' || carac == ')' || carac == ';' || carac == '&' || carac == '|' || carac == '=' ){ ret = -1; break; } } return ret; } static int mail2fax_send ( const char *faxcmd, const char *dest_phone, const char *dest_name, const char *sender_name, const char *sender_adr, const char *pathtmp) { int ret = -1; int err = 0; const char *args[10]; err |= mail2fax_checkshellchar (dest_phone); err |= mail2fax_checkshellchar (dest_name); err |= mail2fax_checkshellchar (sender_name); err |= mail2fax_checkshellchar (sender_adr); args[0] = faxcmd; args[1] = dest_phone; args[2] = dest_name; args[3] = sender_name; args[4] = sender_adr; args[5] = pathtmp; args[6] = NULL; if (err == 0){ pid_t pid = fork(); if (pid == 0){ execv (args[0],(char**)args); fprintf (stderr,"Can't execute %s(%s)\n" ,args[0],strerror(errno)); syslog (LOG_ERR,"Can't execute %s(%m)",args[0]); _exit (1); }else if (pid == -1){ syslog (LOG_ERR,"Can't fork (%m)"); }else{ int status; while (wait (&status) != pid); ret = status; } }else{ syslog (LOG_ERR,"Fax with invalid characters: rejected"); syslog (LOG_ERR,"phone=%s",dest_phone); syslog (LOG_ERR,"recipient name=%s",dest_name); syslog (LOG_ERR,"sender name=%s",sender_name); syslog (LOG_ERR,"sender email=%s",sender_adr); } return ret; } int main (int argc, char *argv[]) { int ret = -1; openlog ("mail2fax",LOG_PID,LOG_MAIL); if (argc != 3){ syslog (LOG_ERR,"Invalid arguments: expected name phone-number"); }else{ const char *name = argv[1]; const char *phone = argv[2]; MAILHEADER head; if (strlen(phone)>=MAXHLINE){ fprintf (stderr,"Mail2fax gateway: Phone number too long\n"); }else if (strlen(name)>=MAXHLINE){ fprintf (stderr,"Mail2fax gateway: Name too long\n"); }else{ FAX_DEST dest; strcpy (dest.name,name); strcpy (dest.phone,phone); dest.rule[0] = '\0'; if (mail2fax_getheader(head)!=-1){ FILE *fout; syslog (LOG_ERR,"from = %s, to = %s",head.from.adr,head.to.adr); sprintf (dest.pathtmp,"/var/run/mail2fax-%d.t",getpid()); fout = fopen (dest.pathtmp,"w"); if (fout == NULL){ syslog (LOG_ERR,"Can't open temp file %s (%m)" ,dest.pathtmp); }else{ char buf[300]; CONFPARM cnf; while (fgets(buf,sizeof(buf)-1, stdin)!=NULL){ fputs (buf,fout); } fclose (fout); if (mail2fax_accessok(&head,&dest) != -1){ mail2fax_getconfparm (&cnf); ret = mail2fax_send (cnf.faxcmd ,dest.phone,dest.name ,head.from.name,head.from.adr ,dest.pathtmp); unlink (dest.pathtmp); } } } } } return ret != 0 ? 1 : 0; }