#/* Module Name: sndmsg.c Installation: if $1e = finale goto finale cc -c sndmsg.c if ! -r sndmsg.c exit cc sndmsg.o /usr/net/hnconv.c rm -f sndmsg.o hnconv.o exit : finale cc -O -c -s sndmsg.c if ! -r sndmsg.o exit cc -O -s sndmsg.o /usr/net/hnconv.c rm -f sndmsg.o hnconv.o if ! -r a.out exit su cp a.out /bin/sndmsg rm a.out Module History: (((Most recent changes first))) change NOSC-SDL to be the HOSTNAME value in siteinfo.h; also to get the local IMP number from NETNUMBER in siteinfo.h changed Rand-Unix to be NOSC-SDL change RAND-ISD to Rand-Unix in 'from' only snoop if SNOOPFILE exists restore @ as line-delete before asking for subject, not after added 'copy of mail for xxx' to messages for snoop copy restore @ as line-delete before exiting if no 'To' list given changed SNOOP to MAILCOPY added message separaters added ability to be called by server FTP added aliases added mail snoop change '@' to ' at ' in to list */ #include "/usr/sys/h/siteinfo.h" #define MSGSEP "\001\001\001\001\n" #define MSGSEPL 5 #define loop for(;;) #define SNOOPFILE "/change/snoop.bak" /* if this file is there, then snoop */ #define SNOOP "Mailcopy@RCC\n" /* 0 or snoop mailbox name ended with \n */ #define LOCIMP (NETNUMBER&077) /* local imp number */ #define true 1 #define false 0 #define SMALLBUFLEN 256 #define controla 001 #define controlb 002 #define controlc 003 #define controld 004 #define controlf 006 #define controlh 010 #define controlq 021 #define controlr 022 #define controls 023 #define controlw 027 #define controlx 030 #define controlz 032 #define backspace 036 #define del 0177 #define abortchar 034 int goaway(); struct status { char s_minor; char s_major; int s_number; int s_mode; char s_nlinks; char s_uid; char s_gid; char s_size0; int s_size1; int s_addr[8]; int s_actime[2]; int s_modtime[2]; } statb; struct { char ispeed; char ospeed; char erase; char kill; int mode; } *sttyptr &ttys; struct iobuf { int fildes; int nleft; char *nextp; char buff[512]; } afilebuf; struct iobuf *afileptr &afilebuf; int savekill; int pwfd; int afile; int tmpfd; int fd; int namefd; int mbfd; char bigbuf[512]; char pwbuf[512]; char *pwptr; int pwcount; int filecnt; int netmsgs; char cc[SMALLBUFLEN],to[SMALLBUFLEN],subject[SMALLBUFLEN]; char answer[10]; char user[40]; char dbuf[32]; char tempfile[64],mailbox[64],netfile[64],from[64],returnto[64]; char hostname[64]; char realname[80]; char *letter,*date; int ttys[3]; int oldmode,uid,gid,myuid; int unsent false; int snoopcopy false; int snoop false; /* becomes true if any non-Rand recipients */ int file false; int oldfile false; int orighost; char hex[] "0123456789ABCDEF"; /* name: send function: A message sending program modeled after the tenex program sndmsg. parameters: None have been defined as of yet. returns: It delivers local mail by appending it to a file called mailbox in the user's root. The delivery of network mail is not yet implemented, but will probably by dropping the file into a daemons mailbox. If mail is undeliverable, a copy of the letter is left in a file called unsent.mail, in the senders current directory. history: Originally written by someone at Berkeley (Murray Bowles?) Reorganized and augmented by Mark Kampe 10/75 */ main(argc,argv) char *argv[]; int argc; { char *lp; register char *p1, *p2; int register i; int vector[2]; if ((pwfd = open("/etc/passwd",0)) < 0) { printf("Couldn't open password file!\n"); exit(); } afile=fopen("/usr/net/aliases",afileptr); if (argc==3 && *argv[0]=='\001') /* is being called by FTP */ { if ((tmpfd=open(argv[1],0)) < 0) /* second argument is file name */ exit(); orighost=argv[0][1]; /* originating host */ returnto[0] = '\0'; p1 = "mailer"; p2 = from; while(*p1++ = *p1++); send(argv[2],0); /* third argument is user name */ if ((orighost&077) != LOCIMP) snoop++; snoopcopy = argv[2]; if (snoop && SNOOP && stat(SNOOPFILE,&statb) >= 0) send(SNOOP,0); close(tmpfd); unlink(argv[1]); exit(); } getname(from); findlocal(from); p1 = mailbox; p2 = returnto; while(*p2++ = *p1++); /* save user's directory for returning undeliverable*/ time(vector); /* mail */ chkill(); /* remove @ as line delete character */ date = arpadate(vector); printf("To: "); gather(to); if (*to == '\000') { printf("Can't send mail to nobody!\n"); rstkill(); exit(); } printf("cc: "); gather(cc); rstkill(); /* restore line delete */ printf("Subject: "); gather(subject); printf("Type letter:\n-----\n"); input(); send(to,1); send(cc,1); if (snoop && SNOOP && stat(SNOOPFILE,&statb) >= 0) {returnto[0] = '\0'; /* don't want to return this copy */ send(SNOOP,0); /* printf("A copy of this message is being sent to correspondence\n");*/ } if (unsent) { fd = creat("unsent.mail",0644); if (fd < 0) { printf("unable to create unsent .mail\n"); exit(1); } writef(fd,letter); close(fd); chown("unsent.mail",myuid); printf("A copy of this letter has been left in 'unsent.mail'\n"); } close(tmpfd); unlink(tempfile); exit(0); /* [I don't know about this part!] if (netmsgs) { printf("Send network mail now? "); gather(answer); if (answer[0] != 'y') exit(0); else execl("/usr/bin/maildaemon", "send netmail", 0); } */ } /* name: gather function: read a single line of console input into the user specified buffer. Currently standard unix editing is in effect, but this will later be changed to use the same sexy editing as does the message inputting routine. algorithm: Read a character: If it is an unescaped new line, null terminate the buffer and return. Otherwise, stash the character into the buffer. If the length of the buffer has been reached, echo a new line and return. parameters: *char pointer to user buffer of length SMALLBUFLEN returns: nothing globals: SMALLBUFLEN calls: getchar slashc called by: main send history: Recoded by Mark Kampe 11/22/75 */ gather(sp) char *sp; { register char c; register char *p; p = sp; while( ((c = getchar(0)) != '\n') && (c>0) ) { if (c == '\\') *p++ = slashc(); else *p++ = c; if ((p - sp) >= SMALLBUFLEN) break; } *p = '\0'; } /* name: writef function: Write a null terminated string algorithm: Determine the length of the string. Write it out. CAVEAT This call is not buffered, so use it carefully or pay the cost of high I/O expense. It was writted to decrease subroutine linkage global variable referencing and module size. But those wins can easily be lost if writef is consistantly called with small strings. parameters: int file descriptor of output file. *char pointer to null terminated string. returns: int value returned by write. globals: calls: write (system) called by: lotsa people history: Initially coded by Mark Kampe 11/22/75 */ writef(afd,str) int afd; char *str; { register int count; register char *s; s = str; for(count=0; *s++; count++); return(write(afd,str,count)); } writef1(afd,str) /* same as writef except convert '@' to ' at ' */ int afd; char *str; { char c; while (c = *str++) { if (c == '@') write(afd," at ",4); else write(afd,&c,1); } } /* name: input function: To accept the text of the message while doing all of the sexy sndmsg type editing. algorithm: Allocate a huge buffer. Read characters (editing sexily as I go) Return when I get a ctl-d or ctl-z parameters: none returns: With a null terminated letter in the string pointed to by the global variable letter. globals: letter address of the buffer. calls: rawmode normal writef write system printf getchar sbreak system gtty system called by: main history: Originally coded by someone at Berkeley Fixed up and augmented by Mark Kampe. */ input() { char *tp; register char *p,c; register int i; int eomem; char filename[50]; p = letter = sbrk(1000); /* Tried to avoid this! */ eomem = letter+1000; gtty(0,ttys); rawmode(); loop { c = getchar(0); if (p >= eomem) { sbrk(1000); eomem =+ 1000; } switch (c) { case controla: /* tty oriented character delete */ if (p > letter) { write(1,"\\",1); write(1,--p,1); } continue; case controlb: /* input a file at this point */ case controlf: printf("\nInput file: "); normal(); gather(filename); if ((fd = open(filename,0)) < 0) printf("Couldn't open %s\n",filename); else { sbrk(512); eomem =+ 512; for(i=read(fd,p,512); i>0; i=read(fd,p,512)) { p =+ i; sbrk(512); eomem =+ 512; } close(fd); printf("%s has been included\n", filename); } rawmode(); continue; case controlc: /* breakout */ case del: normal(); exit(); case controld: /* normal end of message */ case controlz: if (*(p-1) != '\n') { *p++ = '\n'; printf("\n"); /* Guarentee msg ends with a new line */ } *p = '\000'; printf("-----\n"); goto out; case backspace: case controlh: if (p > letter) write(1," \b",2); case '#': if (p > letter) p--; continue; case controlr: /* retype current line */ tp = p; while ((tp > letter) && (*tp != '\n')) tp--; *p = '\0'; if (tp == letter) write(1,"\n",1); writef(1,tp); continue; case controls: /* retype entire message */ *p = '\000'; write(1,"\n",1); writef(1,letter); continue; case controlw: /* back up over the last word typed */ --p; while((p > letter) && ((*p != ' ') && (*p != ',') && (*p != '\n') && (*p != '\t') ) ) { write(1,"\b \b",3); p--; }; while ((p>letter) && ((*p == ' ') || (*p == '\t')) ) { write(1,"\b \b",3); p--; }; *++p = '\0'; continue; case controlx: /* line delete */ write(1,"XXX",3); case '@': write(1,"\n",1); p--; while ((p >= letter) && (*p != '\n')) p--; *++p = '\0'; continue; case abortchar: /* sigh */ normal(); abort(); case '\\': c = slashc(); default: *p++ = c; } /* end switch */ } /* end of while loop */ out: /* restore console modes and make the tempfile */ normal(); tmpfile(tempfile); /* get a filename */ if ((tmpfd = creat(tempfile,0644)) < 0) { printf("Unable to create temp file\n"); exit(1); } write(tmpfd,"Date: ",6); writef(tmpfd,date); write(tmpfd,"\nFrom: ",7); writef(tmpfd,from); writef(tmpfd," at "); writef(tmpfd,HOSTNAME); if (*subject) { write(tmpfd,"\nSubject: ",10); writef(tmpfd,subject); } write(tmpfd,"\nTo: ",5); writef1(tmpfd,to); if (*cc) { write(tmpfd,"\ncc: ",5); writef1(tmpfd,cc); } write(tmpfd,"\n\n",2); writef(tmpfd,letter); write(tmpfd,"-------\n",8); /* whole message is now in temp file */ /* close it and reopen it for input */ close(tmpfd); tmpfd = open(tempfile,0); } /* end of input */ /* name: rawmode, normal, chkill, and rstkill function: to put the console into raw mode and normal mode and to change and restore the line delete character. globals: oldmode calls: stty called by: input history: written at berkeley */ normal() { ttys[2] = oldmode; stty(0,ttys); } rawmode() { oldmode = ttys[2]; ttys[2] =| 040; /* raw mode */ stty(0,ttys); } chkill() { gtty(0,ttys); savekill = ttys[1]; sttyptr->kill = '\177'; /* change line delete to nothing! */ signal(2,&goaway); stty(0,ttys); } rstkill() { ttys[1] = savekill; stty(0,ttys); signal(2,1); signal(3,1); } goaway() { ttys[1] = savekill; stty(0,ttys); exit(); } /* name: send function: To take a list of recipients and take steps to deliver the message (now in the temp file) to all of them. algorithm: parse off a name. If it is a name:, open that file and read from it for a while. If it is a local user id, call post If it is a network user id, call netpost parameters: Pointer to a null terminated characterstring which is a list of recipients. returns: globals: unsent calls: valid open (system) printf network findlocal netpost post called by: main history: Coded by someone at berkely. Minor modifications by Mark Kampe. */ send(list,infoflg) char *list; int infoflg; { register char *name,*curp; register char c; int ret; int alias; char *ap; alias = false; curp = list; while (alias || file || *curp) { name = user; while (valid(c = file?getchar(namefd):(alias?*ap++:*curp++))) *name++ = c; *name = '\0'; switch (c) { case ':': if (file) {if (infoflg) printf("Nested name files not allowed.\n"); continue; } if ((namefd = open(user,0)) < 0) { if (infoflg) printf("Couldn't open %s\n",user); unsent++; continue; } file++; continue; case -1: close(namefd); file = false; continue; case '\0': if (!(file|alias)) --curp; case '\n': if (!file && alias) alias = false; case ',': case ' ': if (*user == '\000') continue; if (infoflg) printf("%s -- ",user); if (ret=network(user)) { if (ret == -1) { if (infoflg) printf("host unknown\n"); unsent++; continue; } netpost(user,hostname,infoflg); continue; } if (!alias && findalias(user)) { alias++; ap = realname; if (infoflg) printf("(alias for %s)\n",realname); continue; } if (findlocal(user)) { stcat("/.mail",mailbox); post(infoflg); continue; } { if (infoflg) printf("not a local user\n"); unsent++; continue; } default: if (infoflg) {printf("Illegal terminator %c.\n",c); printf("User name %s ignored.\n",user); } } /* end switch */ } } /* end send */ /* name: findlocal function: To ascertain whether or not someone is a local user, and if he is, to find the name of his root directory. algorithm: Rewind the password file. Search it for a line starting with the user name. If found: extract all the info about uid, gid mailbox, etc. return. Else, continue the search. Announce that he isn't a known user. parameters: *char pointer to null terminated user name. returns: boolean whether or not he was found. globals: pwfd uid gid mailbox pwcount calls: getpwchar seek (system) called by: send history: Written by Mark Kampe */ findlocal(name) char *name; { register char *np; register char pwc; /* rewind the password file */ seek(pwfd,0,0); pwcount = 0; pwc = 1; while(pwc > 0) { np = name; /* search for a line starting with name */ for(pwc = getpwchar(); pwc == *np++; pwc = getpwchar()); if ((pwc == ':') && (*--np == '\000')) goto gotuser; while( (pwc != '\n') && (pwc > 0) ) pwc = getpwchar(); } return(false); gotuser: /* extract all neat info from pw entry */ for(pwc = getpwchar(); pwc != ':'; pwc = getpwchar()); /* we have skipped over the password */ for(uid = 0; (pwc = getpwchar()) != ':'; uid =+ (9*uid) + pwc - '0'); for(gid = 0; (pwc = getpwchar()) != ':'; gid =+ (9*gid) + pwc - '0'); for(pwc = getpwchar(); pwc != ':'; pwc = getpwchar()); /* we have skipped over the gecos bin */ np = mailbox; for(pwc = getpwchar(); pwc != ':'; pwc = getpwchar()) *np++ = pwc; *np = '\000'; return(true); } /*name: findalias function: To determine whether a name is in the alias file, and if so to replace it with the real name. algorithm: Search the file for a line beginning with the name. If found, move the real name(s) to 'realname'. globals: afile afileptr realname calls: getc(III) seek(II) called by: send history: greep */ findalias(name) char *name; { register char c, *p; if (afile<0) return(false); /* alias file could not be opened */ seek(afileptr->fildes,0,0); loop { for (p=name; *p++ == (c=getc(afileptr));); /* check for same name */ if (c<0) return(false); if ((*--p == '\0') && (c == ':')) break; /* got it */ while(getc(afileptr) != '\n'); /* get to end of this line */ } p = realname; /* move stuff after : to realname */ while ((*p++ = getc(afileptr)) != '\n'); *--p = '\0'; *++p = '\n'; return(true); } /* name: network function: To determine whether a user is a network user, and if he is, to break the name up into a hostname and ultimately a list of user names. algorithm: Look for an @ in the name, if not found, not net Copy the hostname into a separate buffer and truncate it off of the user name. parameters: *char pointer to null terminated user name. returns: boolean Whether or not guy was a network user. (will be -1 if network site name unknown) and if he was: Hostname has been truncated off of user name and put into the global buffer hostname. globals: hostname. calls: hnconf (not part of this source) to verify host name called by: send history: Written by Mark Kampe 11/27/75 */ network(name) char *name; { register char *c; register char *d; int hostnum; for(c = name; *c; c++) if (*c == '@') goto netname; return(false); netname: *c++ = '\000'; d = hostname; while(*d++ = *c++); if ((hostnum=atoi(hnconv(hostname))) <= 0) return(-1); if ((hostnum&077) != LOCIMP) snoop++; /* check for rand (any host) */ return(true); } /* name: tempfile function: To select a unique name for and creat a temporary file. algorithm: Use send<processid> as the filename. parameters: *char pointer to user buffer of adequate length. returns: *char pointer to the chosen name and tmpfd is the file descriptor for that file. globals: tmpfd calls: creat getpid called by: input history: Coded by someone at Berkeley */ tmpfile(ubuf) char *ubuf; { register int i; register char *p1; register char *p2; p1 = ubuf; for(p2 = "/tmp/send"; *p2 ; *p1++ = *p2++); p2 = getpid(); for(i=0; i<5; i++) { *p1++ = (p2 & 07) + '0'; p2 =>> 3; } return(ubuf); } /* name: post function: To mail a letter (in tempfile) to some individual unix user. algorithm: Create a mailbox if he doesn't have one. If he had one make sure it is not in use. Append the contents of the tempfile to it. Make recipient the owner if he wasn't already. parameters: none but.. null terminated name of recipients directory is in in mailbox. returns: boolean Whether or not it was successfully sent. globals: mailbox mbfd tmpfd bigbuf unsent calls: creat seek read write printf stcat called by: send history: Rewritten by Mark Kampe 11/24/75 */ post(infoflg) int infoflg; { register int i; if (stat(mailbox,&statb) < 0) { mbfd = creat(mailbox,0666); if (mbfd < 0) { if (infoflg) printf("Unable to create mailbox\n"); unsent++; return(false); } } else { /*** if (statb.s_nlinks > 1) { if (infoflg) printf("Mailbox is busy\n"); unsent++; return(false); } ***/ mbfd = open(mailbox,1); if (mbfd < 0) { if (infoflg) printf("Mailbox is busy\n"); unsent++; return(false); } seek(mbfd,0,2); /* seek end of file */ } seek(tmpfd,0,0); /* rewind the file containing the message */ write(mbfd,MSGSEP,MSGSEPL); /* writef(mbfd,"Mail received for "); writef(mbfd,user); writef(mbfd,"\n"); */ i = read(tmpfd,bigbuf,512); while(i > 0) { write(mbfd,bigbuf,i); i = read(tmpfd,bigbuf,512); } write(mbfd,MSGSEP,MSGSEPL); close(mbfd); chown(mailbox,uid); if (infoflg) printf("ok\n"); return(true); } /* end post */ /* name: netpost function: to attempt to mail off a piece of network mail. algorithm: Contrive a unique name for a network mail file. Create it in the netmail daemon's mailbox put destination host, users and sender names into file. copy message into the file. parameters: *char pointer to null terminated user name *char pointer to null terminated host name returns: boolean whether or not it worked globals: unsent netmsgs count of number of net msgs so far tmpfd bigbuf calls: read write printf creat close stat chown called by: send history: Initially coded by Mark Kampe */ netpost(uname,hname,infoflg) char *uname; char *hname; int infoflg; { register char *p1; register char *p2; register int i; p2 = netfile; for(p1 = "/usr/netmail/"; *p1; *p2++ = *p1++); crname(p2); mbfd = creat(netfile, 0444); if (mbfd < 0) { if (infoflg) printf("Unable to create mailfile for %s@%s\n", uname,hname); unsent++; return(false); } writef(mbfd,hname); write (mbfd,":",1); writef(mbfd,uname); write (mbfd,":",1); writef(mbfd,returnto); write (mbfd, ":\n",2); seek(tmpfd, 0, 0); if (snoopcopy) { writef(mbfd,"Copy of mail received for "); writef(mbfd,snoopcopy); writef(mbfd,"\n"); } for(i = read(tmpfd,bigbuf,512); i; i = read(tmpfd,bigbuf,512)) write(mbfd,bigbuf,i); close(mbfd); chown(netfile,myuid); if (infoflg) printf("queued\n"); netmsgs++; return(true); } /* name: getname function: To find out the name of the user who forked me. algorithm: Get my real user id and look it up in the password table parameters: *char pointer to buffer where name should be placed. returns: *char pointer to buffer wher callers's name was placed. globals: calls: getuid getpw printf called by: main history: Rewritten by Mark Kampe */ getname(caller) char *caller; { char linebuf[100]; char *cp,*lp; myuid = getuid() & 0377; if (getpw(myuid, linebuf)) { printf("Your user id is not in the password file?!?\n"); exit(); } cp = caller; lp = linebuf; while(*lp != ':') *cp++ = *lp++; *cp = '\000'; return(caller); } /* name: slashc function: Given that the last input character seen was a backslash, this routine returns the character that is being escaped. algorithm: If terminal is half ascii If next char is lower case, return its upper case counterpart. If it is the preimage of a non-half ascii character, return that character. Else, return the next character. parameters: returns: A character. globals: ttys to check out the console modes. calls: getchar called by: people who do rawmode input (namely input) history: Initial coding by someone at berkeley. */ slashc() { char c; c = getchar(0); if (ttys[2] & 04) /* Half-ASCII */ { if (c >= 'a' && c <= 'z') return(c-'a'+'A'); switch (c) { case '!': return('|'); case '\'': return('`'); case '^': return('~'); case '(': return('{'); case ')': return('}'); default: return(c); } /* end switch */ } /* end half-ASCII */ else return(c); } /* end slashc */ /* name: getchar function: To read a character from a specified file. algorithm: Do a read, and if we got an end of file, return a -1. CAVEAT Input is unbuffered. parameters: int file descriptor of file to be read from. returns: char or -1; globals: calls: read (system) called by: lots of people history: Someone at berkeley. */ char getchar(cfd) { char c; register int i; i = read(cfd,&c,1); if (i <= 0) return(-1); else return(c&0177); } char getpwchar() { tryagain: if (pwcount > 0) { pwcount--; return(*pwptr++); } else { pwcount = read(pwfd,pwbuf,512); if (pwcount <= 0) return(-1); pwptr = pwbuf; goto tryagain; } } valid(ch) char ch; { return(((ch >= 'a') && (ch <= 'z')) | ((ch >= '0') && (ch <= '9')) | (ch == '.') | (ch == '/') | (ch == '-') | (ch == ';') | (ch == '@') | (ch == '(') | (ch == ')') | ((ch >= 'A') && (ch <= 'Z')) ); } stcat(latter,former) char *latter,*former; { register char *fp,*lp; fp = former; lp = latter; while (*fp++); fp--; while (*fp++ = *lp++); } /* name: arpadate function: to get an ARPA format date (format described in rfc 680) algorithm: Get the unix format date and jumble it around to be arpa format Unix format is: day mmm dd hh:mm:ss yyyy Arpa format is: dd mmm yyyy at hhmm-zzz parameters: pointer to the doubleword time returns: pointer to the arpa format date corresponding to that time globals: dbuf calls: ctime localtime called by: main history: Designed and coded by mark Kampe 1/9/76 */ char *arpadate(tvec) int tvec[2]; { register int *i; register char *p; register char *t; t = ctime(tvec); p = dbuf; *p++ = t[8]; *p++ = t[9]; *p++ = ' '; *p++ = t[4]; *p++ = t[5]; *p++ = t[6]; *p++ = ' '; *p++ = t[20]; *p++ = t[21]; *p++ = t[22]; *p++ = t[23]; *p++ = ' '; *p++ = 'a'; *p++ = 't'; *p++ = ' '; *p++ = t[11]; *p++ = t[12]; *p++ = t[14]; *p++ = t[15]; *p++ = '-'; *p++ = 'P'; i = localtime(tvec); if (i[8]) *p++ = 'D'; else *p++ = 'S'; *p++ = 'T'; *p++ = '\000'; return(dbuf); } /*name: function: to create a unique file name algorithm: use the process id number, the date and time, and a reference count paramters: pointer to where the name is to be stored globals: hex calls: getpid time called by: netpost history: greep */ crname(q) char *q; { int i; int tvec[4]; char *p; p = &tvec[0]; tvec[2] = getpid(); tvec[3] = filecnt++; if (filecnt==256) {filecnt = 0; sleep(1); } time(tvec); for (i=7; i; --i) { *q++ = hex[(*p>>4)&017]; *q++ = hex[ *p++ &017]; } *q = '\0'; }