diff options
Diffstat (limited to 'ktalkd/ktalkd/machines/answmach.cpp')
-rw-r--r-- | ktalkd/ktalkd/machines/answmach.cpp | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/ktalkd/ktalkd/machines/answmach.cpp b/ktalkd/ktalkd/machines/answmach.cpp new file mode 100644 index 00000000..0e36c3e2 --- /dev/null +++ b/ktalkd/ktalkd/machines/answmach.cpp @@ -0,0 +1,405 @@ +/* + * 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 kdelibs/doc/common/bsd-license.html) + * + */ + +#include "answmach.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <pwd.h> +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#include <time.h> +#include <sys/ioctl.h> +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#include <fcntl.h> +#include <errno.h> +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <syslog.h> +#include <netdb.h> +#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; i<nb; i++ ) { + if ((buff[i]==char_erase) && (pos>0)) /* 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); + } + } + |