1 /*- 2 * Copyright (c) 1990 The Regents of the University of California. 3 * All rights reserved. 4 * 5 * %sccs.include.redist.c% 6 */ 7 8 #ifndef lint 9 char copyright[] = 10 "@(#) Copyright (c) 1990 The Regents of the University of California.\n\ 11 All rights reserved.\n"; 12 #endif /* not lint */ 13 14 #ifndef lint 15 static char sccsid[] = "@(#)mail.local.c 4.38 (Berkeley) 01/17/91"; 16 #endif /* not lint */ 17 18 #include <sys/param.h> 19 #include <sys/stat.h> 20 #include <sys/file.h> 21 #include <sys/socket.h> 22 #include <sys/syslog.h> 23 #include <sys/errno.h> 24 #include <netinet/in.h> 25 #include <netdb.h> 26 #include <pwd.h> 27 #include <time.h> 28 #include <varargs.h> 29 #include <stdio.h> 30 #include <string.h> 31 #include <stdlib.h> 32 #include "pathnames.h" 33 34 #define FATAL 1 35 #define NOTFATAL 0 36 37 main(argc, argv) 38 int argc; 39 char **argv; 40 { 41 extern int optind; 42 extern char *optarg; 43 struct passwd *pw; 44 int ch, fd, eval; 45 uid_t uid; 46 char *from; 47 48 openlog("mail.local", LOG_PERROR, LOG_MAIL); 49 50 from = NULL; 51 while ((ch = getopt(argc, argv, "df:r:")) != EOF) 52 switch(ch) { 53 case 'd': /* backward compatible */ 54 break; 55 case 'f': 56 case 'r': /* backward compatible */ 57 if (from) 58 error(FATAL, "multiple -f options."); 59 from = optarg; 60 break; 61 case '?': 62 default: 63 usage(); 64 } 65 argc -= optind; 66 argv += optind; 67 68 if (!*argv) 69 usage(); 70 71 /* 72 * If from not specified, use the name from getlogin() if the 73 * uid matches, otherwise, use the name from the password file 74 * corresponding to the uid. 75 */ 76 uid = getuid(); 77 if (!from || !(from = getlogin()) || 78 !(pw = getpwnam(from)) || pw->pw_uid != uid) 79 from = (pw = getpwuid(uid)) ? pw->pw_name : "???"; 80 81 fd = store(from); 82 for (eval = 0; *argv; ++argv) 83 eval |= deliver(fd, *argv); 84 exit(eval); 85 } 86 87 store(from) 88 char *from; 89 { 90 FILE *fp; 91 time_t tval; 92 int fd, eline; 93 char *tn, line[2048]; 94 95 tn = _PATH_LOCTMP; 96 if ((fd = mkstemp(tn)) == -1 || !(fp = fdopen(fd, "w+"))) 97 error(FATAL, "unable to open temporary file."); 98 (void)unlink(tn); 99 100 (void)time(&tval); 101 (void)fprintf(fp, "From %s %s", from, ctime(&tval)); 102 103 line[0] = '\0'; 104 for (eline = 1; fgets(line, sizeof(line), stdin);) { 105 if (line[0] == '\n') 106 eline = 1; 107 else { 108 if (eline && line[0] == 'F' && !bcmp(line, "From ", 5)) 109 (void)putc('>', fp); 110 eline = 0; 111 } 112 (void)fprintf(fp, "%s", line); 113 if (ferror(fp)) 114 break; 115 } 116 117 /* If message not newline terminated, need an extra. */ 118 if (!index(line, '\n')) 119 (void)putc('\n', fp); 120 /* Output a newline; note, empty messages are allowed. */ 121 (void)putc('\n', fp); 122 123 (void)fflush(fp); 124 if (ferror(fp)) 125 error(FATAL, "temporary file write error."); 126 return(fd); 127 } 128 129 deliver(fd, name) 130 int fd; 131 char *name; 132 { 133 struct stat sb; 134 struct passwd *pw; 135 int created, mbfd, nr, nw, off, rval; 136 char biffmsg[100], buf[8*1024], path[MAXPATHLEN]; 137 off_t curoff, lseek(); 138 139 /* 140 * Disallow delivery to unknown names -- special mailboxes can be 141 * handled in the sendmail aliases file. 142 */ 143 if (!(pw = getpwnam(name))) { 144 error(NOTFATAL, "unknown name: %s.", name); 145 return(1); 146 } 147 148 (void)sprintf(path, "%s/%s", _PATH_MAILDIR, name); 149 150 if (!(created = lstat(path, &sb)) && 151 (sb.st_nlink != 1 || S_ISLNK(sb.st_mode))) { 152 error(NOTFATAL, "%s: linked file.", path); 153 return(1); 154 } 155 156 /* 157 * There's a race here -- two processes think they both created 158 * the file. This means the file cannot be unlinked. 159 */ 160 if ((mbfd = 161 open(path, O_APPEND|O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR)) < 0) { 162 error(NOTFATAL, "%s: %s.", path, strerror(errno)); 163 return(1); 164 } 165 166 rval = 0; 167 /* XXX: Open should allow flock'ing the file; see 4.4BSD. */ 168 if (flock(mbfd, LOCK_EX)) { 169 error(NOTFATAL, "%s: %s.", path, strerror(errno)); 170 rval = 1; 171 goto bad; 172 } 173 174 curoff = lseek(mbfd, 0L, SEEK_END); 175 (void)sprintf(biffmsg, "%s@%ld\n", name, curoff); 176 if (lseek(fd, 0L, SEEK_SET) == (off_t)-1) { 177 error(FATAL, "temporary file: %s.", strerror(errno)); 178 rval = 1; 179 goto bad; 180 } 181 182 while ((nr = read(fd, buf, sizeof(buf))) > 0) 183 for (off = 0; off < nr; nr -= nw, off += nw) 184 if ((nw = write(mbfd, buf + off, nr)) < 0) { 185 error(NOTFATAL, 186 "%s: %s.", path, strerror(errno)); 187 goto trunc; 188 } 189 if (nr < 0) { 190 error(FATAL, "temporary file: %s.", strerror(errno)); 191 trunc: (void)ftruncate(mbfd, curoff); 192 rval = 1; 193 } 194 195 /* 196 * Set the owner and group. Historically, binmail repeated this at 197 * each mail delivery. We no longer do this, assuming that if the 198 * ownership or permissions were changed there was a reason for doing 199 * so. 200 */ 201 bad: if (created) 202 (void)fchown(mbfd, pw->pw_uid, pw->pw_gid); 203 204 /* Implicit unlock. */ 205 (void)close(mbfd); 206 207 if (!rval) 208 notifybiff(biffmsg); 209 return(rval); 210 } 211 212 notifybiff(msg) 213 char *msg; 214 { 215 static struct sockaddr_in addr; 216 static int f = -1; 217 struct hostent *hp; 218 struct servent *sp; 219 int len; 220 221 if (!addr.sin_family) { 222 /* Be silent if biff service not available. */ 223 if (!(sp = getservbyname("biff", "udp"))) 224 return; 225 if (!(hp = gethostbyname("localhost"))) { 226 error(NOTFATAL, "localhost: %s.", strerror(errno)); 227 return; 228 } 229 addr.sin_family = hp->h_addrtype; 230 bcopy(hp->h_addr, &addr.sin_addr, hp->h_length); 231 addr.sin_port = sp->s_port; 232 } 233 if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { 234 error(NOTFATAL, "socket: %s.", strerror(errno)); 235 return; 236 } 237 len = strlen(msg) + 1; 238 if (sendto(f, msg, len, 0, &addr, sizeof(addr)) != len) 239 error(NOTFATAL, "sendto biff: %s.", strerror(errno)); 240 } 241 242 usage() 243 { 244 error(FATAL, "usage: mail.local [-f from] user ..."); 245 } 246 247 /* VARARGS */ 248 error(va_alist) 249 va_dcl 250 { 251 va_list ap; 252 int isfatal; 253 char *fmt; 254 255 va_start(ap); 256 isfatal = va_arg(ap, int); 257 fmt = va_arg(ap, char *); 258 vsyslog(LOG_ERR, fmt, ap); 259 va_end(ap); 260 if (isfatal) 261 exit(1); 262 } 263