/* * Copyright (c) 1983 Regents of the University of California, (c) 1997 David Faure * All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. * * (BSD License, from tdelibs/doc/common/bsd-license.html) * */ #include "answmach.h" #include #include #include #include #ifdef HAVE_SYS_TYPES_H #include #endif #include #include #ifdef HAVE_SYS_STAT_H #include #endif #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include "../defs.h" #include "../proto.h" #include "../readconf.h" #define A_LONG_TIME 10000000 /* seconds before timeout */ /** AnswMachine constructor */ AnswMachine::AnswMachine(struct in_addr r_addr, char * r_name, char * l_name, int _mode) { /* Copy the answering machine mode */ mode = _mode; /* Copy the local user name (i.e. the callee, existent or not) */ strcpy(local_user, r_name); /* Copy the caller's machine address */ caller_machine_addr = r_addr; /* Create a talk connection */ talkconn = new TalkConnection(r_addr, l_name, /* the caller (remote) */ local_user, ntalkProtocol); /* the callee (local) */ if (mode==PROC_REQ_ANSWMACH_NOT_HERE) { /* The caller is trying to talk to somebody this system doesn't know. We can display a NEU banner (non-existent user) and take a message for Options.NEU_user (root?). */ strncpy(NEUperson,local_user,NEW_NAME_SIZE); /* the person the talk was aimed to */ NEUperson[ NEW_NAME_SIZE - 1 ] = '\0'; strncpy(local_user,Options.NEU_user,NEW_NAME_SIZE); /* for mail address, config file... */ local_user[ NEW_NAME_SIZE - 1 ] = '\0'; } else *NEUperson='\0'; } AnswMachine::~AnswMachine() { delete talkconn; } int AnswMachine::LaunchIt(const char * key) { int launchIt = 1; if (usercfg) { read_bool_user_config(key,&launchIt); /* if (launchIt==0) debug("Not launched. Option set to 0 : ", key);*/ } return launchIt; } void AnswMachine::start() { /* Only wait if somebody could possibly answer. (The 'ringing your party again' has just been displayed, we want to leave a second chance for the callee to answer.) If NEU/not logged, start quickly. (Wait just a little for the LEAVE_INVITE to come.) */ sleep( (mode==PROC_REQ_ANSWMACH) ? Options.time_before_answmach : 1 ); usercfg = init_user_config(local_user); if (LaunchIt("Answmach")) { talkconn->open_sockets(); if (talkconn->look_for_invite(1/*mandatory*/)) /* otherwise, either the caller gave up before we started or the callee answered ... */ { /* There was an invitation waiting for us, * so connect with the other (hopefully waiting) party */ if (talkconn->connect()) { /* send the first 3 chars, machine dependent */ talkconn->set_edit_chars(); /* Do the talking */ talk(); } } } if (usercfg) end_user_config(); } static char * shell_quote(const char *s) { char *result; char *p; p = result = (char *) malloc(strlen(s)*5+1); while(*s) { if (*s == '\'') { *p++ = '\''; *p++ = '"'; *p++ = *s++; *p++ = '"'; *p++ = '\''; } else { *p++ = *s++; } } *p = '\0'; return result; } /** The actual talking (user to answering machine) */ void AnswMachine::talk() { char command[S_COMMAND]; char messg_myaddr [S_MESSG]; struct hostent *hp; FILE * fd = 0; /* file descriptor, to write the message */ char customline[S_CFGLINE]; char fname[ 256 ]; int fildes; int oldumask = umask( 066 ); /* set permissions for temp file to rw- --- --- */ int emptymail; /* 1 if empty mail allowed */ int something_entered; hp = gethostbyaddr((char *)&caller_machine_addr, sizeof (struct in_addr), AF_INET); if (hp == (struct hostent *)0) TalkConnection::p_error("Answering machine : Remote machine unknown."); if ((!usercfg) || (!read_user_config("Mail",messg_myaddr,S_MESSG-1))) { strncpy(messg_myaddr, local_user, S_MESSG); messg_myaddr[ S_MESSG - 1 ] = '\0'; } sprintf(fname, _PATH_TMP"/ktalkdXXXXXX"); // ### should use mkstemps if ((fildes = mkstemp(fname)) == -1 || (fd = fdopen(fildes, "w+")) == 0) { TalkConnection::p_error("Unable to open temporary file"); } umask(oldumask); write_headers(fd, hp, messg_myaddr, usercfg); /* read other options before setting usercfg to 0, below. */ if ((!usercfg) || (!read_bool_user_config("EmptyMail",&emptymail))) /* try from user config file, otherwise default : */ emptymail = 1; /* debug("Connection established"); */ if (usercfg) { if (!read_user_config("Msg1",customline,S_CFGLINE-1)) { /* debug("Error reading Msg1");*/ end_user_config(); usercfg=0; } } /* No user-config'ed banner */ if (!usercfg) { /* => Display Options.invitelines */ talkconn->write_banner(Options.invitelines); } else if (mode==PROC_REQ_ANSWMACH_NOT_HERE) { /* => Display Options.NEUBanner* */ talkconn->write_banner(Options.NEUBanner1); talkconn->write_banner(Options.NEUBanner2); talkconn->write_banner(Options.NEUBanner3); } else { int linenr = 1; /* number of the Msg[1-*] line. is set to 0 after displaying banner*/ char m[]="Msg1"; /* used as key to read configuration */ while (linenr) /* still something to write to the caller */ { talkconn->write_banner(customline); /* read next line in custom file. */ m[3]=(++linenr)+'0'; if (!read_user_config(m,customline,S_CFGLINE-1)) linenr=0; /* end of message */ } } /* Banner displayed. Let's take the message. */ something_entered = read_message(fd); fclose(fd); if (something_entered || emptymail) { /* Don't send empty message, except if 'EmptyMail' has been set */ int retcode; char *quoted_fname; char *quoted_messg_myaddr; quoted_fname = shell_quote(fname); quoted_messg_myaddr = shell_quote(messg_myaddr); snprintf(command,S_COMMAND,"cat '%s' | %s '%s'",quoted_fname,Options.mailprog,quoted_messg_myaddr); free(quoted_fname); free(quoted_messg_myaddr); /* XXX: * It looks to me as if we're still running as root when we * get here. So using system() is an extremely _bad_ idea, * especially when we use a user-specified address. What * keeps the user from specifying `rm -rf /` as his * address? Yo, way cool. * DF: doesn't shell_quote address this issue? */ retcode = system(command); if ((retcode==127) || (retcode==-1)) syslog(LOG_ERR,"system() error : %s", strerror(errno)); else if (retcode!=0) syslog(LOG_WARNING,"%s : %s", command, strerror(errno)); } (void)unlink(fname); } void AnswMachine::write_headers(FILE * fd, struct hostent * hp, char * messg_myaddr, int usercfg) { char messg [S_MESSG]; char messg_tmpl [S_MESSG]; char * r_user = talkconn->get_caller_name(); /* if using mail.local, set 'Date:' and 'From:', because they will be missing otherwise */ int ismaillocal = (strstr(Options.mailprog,"mail.local")!=NULL); if (ismaillocal) /* should we check only the end of the name ? */ { time_t tmp = time(0); snprintf(messg,S_MESSG,"Date: %s",ctime(&tmp)); /* \n is included in ctime */ fwrite(messg,strlen(messg),1,fd); /* Date */ snprintf(messg,S_MESSG,"From: %s@%s\n",r_user,hp->h_name); fwrite(messg,strlen(messg),1,fd); /* From */ } snprintf(messg,S_MESSG,"To: %s\n",messg_myaddr); fwrite(messg,strlen(messg),1,fd); /* To */ if ((!usercfg) || (!read_user_config("Subj",messg_tmpl,S_CFGLINE))) /* try from user config file, otherwise default subject: */ strcpy(messg_tmpl,"Message from %s"); snprintf(messg,S_MESSG,messg_tmpl,r_user); fwrite("Subject: ",9,1,fd); fwrite(messg,strlen(messg),1,fd); /* Subject */ fwrite("\n",1,1,fd); if (!ismaillocal) { /* No need to set Reply-To if From has been set correctly */ snprintf(messg,S_MESSG,"Reply-To: %s@%s\n",r_user,hp->h_name); fwrite(messg,strlen(messg),1,fd); /* Reply-To */ } fwrite("\n",1,1,fd); /* empty line -> end of headers */ if ((!usercfg) || (!read_user_config("Head",messg_tmpl,S_CFGLINE))) /* try from user config file, otherwise default headline: */ strcpy(messg_tmpl,"Message left in the answering machine, by %s@%s"); snprintf(messg,S_MESSG,messg_tmpl,r_user,hp->h_name); if (mode==PROC_REQ_ANSWMACH_NOT_HERE) { char tmp[ NEW_NAME_SIZE + 10 ]; snprintf(tmp, NEW_NAME_SIZE + 9, " => '%s'", NEUperson); if( strlen(tmp)+strlen(messg) < S_MESSG ) strcat(messg,tmp); } fwrite(messg,strlen(messg),1,fd); /* First line of the message */ fwrite("\n\n",2,1,fd); } int AnswMachine::read_message(FILE * fd) // returns 1 if something has been entered { int nb; fd_set read_template, read_set; int pos = 0; // position on the line. left=0. int something = 0; // nothing entered by caller up to now. struct timeval wait; char buff[BUFSIZ]; char line[80] = ""; // buffer for current line char char_erase = talkconn->get_char_erase(); FD_ZERO(&read_template); FD_SET(talkconn->get_sockt(), &read_template); for (;;) { read_set = read_template; wait.tv_sec = A_LONG_TIME; wait.tv_usec = 0; nb = select(32, &read_set, 0, 0, &wait); if (nb <= 0) { if (errno == EINTR || errno == EAGAIN) { read_set = read_template; continue; } /* panic, we don't know what happened */ TalkConnection::p_error("Unexpected error from select"); } if (FD_ISSET(talkconn->get_sockt(), &read_set)) { int i; /* There is data on sockt */ nb = read(talkconn->get_sockt(), buff, sizeof buff); if (nb <= 0) { /* debug("Connection closed. Exiting"); */ break; } something = 1; for (i=0; i0)) /* backspace */ pos--; else { if (pos == 79) { fwrite(line,pos,1,fd); pos = 0; } line[pos++]=buff[i]; if (buff[i]=='\n') { fwrite(line,pos,1,fd); pos = 0; } } } } /* if read_set ... */ } if (pos>0) { line[pos++]='\n'; fwrite(line,pos,1,fd); } /* last line */ return something; // 1 if something entered. } /** Create and start a new answering machine from the given info */ void AnswMachine::launchAnswMach(NEW_CTL_MSG msginfo, int mode) { if ((fork()) == 0) /* let's fork to let the daemon process other messages */ { #define satosin(sa) ((struct sockaddr_in *)(sa)) AnswMachine * am = new AnswMachine( (satosin(&msginfo.ctl_addr))->sin_addr, /* Caller's machine address */ msginfo.r_name, msginfo.l_name, mode); am->start(); delete am; // exit the child exit(-1); } }