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 5.5 (Berkeley) 03/07/91"; 16 #endif /* not lint */ 17 18 #include <sys/param.h> 19 #include <sys/stat.h> 20 #include <sys/socket.h> 21 #include <sys/errno.h> 22 #include <netinet/in.h> 23 #include <syslog.h> 24 #include <fcntl.h> 25 #include <netdb.h> 26 #include <pwd.h> 27 #include <time.h> 28 #include <unistd.h> 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.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 = strdup(_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 free(tn); 100 101 (void)time(&tval); 102 (void)fprintf(fp, "From %s %s", from, ctime(&tval)); 103 104 line[0] = '\0'; 105 for (eline = 1; fgets(line, sizeof(line), stdin);) { 106 if (line[0] == '\n') 107 eline = 1; 108 else { 109 if (eline && line[0] == 'F' && !bcmp(line, "From ", 5)) 110 (void)putc('>', fp); 111 eline = 0; 112 } 113 (void)fprintf(fp, "%s", line); 114 if (ferror(fp)) 115 break; 116 } 117 118 /* If message not newline terminated, need an extra. */ 119 if (!index(line, '\n')) 120 (void)putc('\n', fp); 121 /* Output a newline; note, empty messages are allowed. */ 122 (void)putc('\n', fp); 123 124 (void)fflush(fp); 125 if (ferror(fp)) 126 error(FATAL, "temporary file write error."); 127 return(fd); 128 } 129 130 deliver(fd, name) 131 int fd; 132 char *name; 133 { 134 struct stat sb; 135 struct passwd *pw; 136 int created, mbfd, nr, nw, off, rval; 137 char biffmsg[100], buf[8*1024], path[MAXPATHLEN]; 138 off_t curoff, lseek(); 139 140 /* 141 * Disallow delivery to unknown names -- special mailboxes can be 142 * handled in the sendmail aliases file. 143 */ 144 if (!(pw = getpwnam(name))) { 145 error(NOTFATAL, "unknown name: %s.", name); 146 return(1); 147 } 148 149 (void)sprintf(path, "%s/%s", _PATH_MAILDIR, name); 150 151 if (!(created = lstat(path, &sb)) && 152 (sb.st_nlink != 1 || S_ISLNK(sb.st_mode))) { 153 error(NOTFATAL, "%s: linked file.", path); 154 return(1); 155 } 156 157 /* 158 * There's a race here -- two processes think they both created 159 * the file. This means the file cannot be unlinked. 160 */ 161 if ((mbfd = 162 open(path, O_APPEND|O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR)) < 0) { 163 error(NOTFATAL, "%s: %s.", path, strerror(errno)); 164 return(1); 165 } 166 167 rval = 0; 168 /* XXX: Open should allow flock'ing the file; see 4.4BSD. */ 169 if (flock(mbfd, LOCK_EX)) { 170 error(NOTFATAL, "%s: %s.", path, strerror(errno)); 171 rval = 1; 172 goto bad; 173 } 174 175 curoff = lseek(mbfd, 0L, SEEK_END); 176 (void)sprintf(biffmsg, "%s@%ld\n", name, curoff); 177 if (lseek(fd, 0L, SEEK_SET) == (off_t)-1) { 178 error(FATAL, "temporary file: %s.", strerror(errno)); 179 rval = 1; 180 goto bad; 181 } 182 183 while ((nr = read(fd, buf, sizeof(buf))) > 0) 184 for (off = 0; off < nr; nr -= nw, off += nw) 185 if ((nw = write(mbfd, buf + off, nr)) < 0) { 186 error(NOTFATAL, 187 "%s: %s.", path, strerror(errno)); 188 goto trunc; 189 } 190 if (nr < 0) { 191 error(FATAL, "temporary file: %s.", strerror(errno)); 192 trunc: (void)ftruncate(mbfd, curoff); 193 rval = 1; 194 } 195 196 /* 197 * Set the owner and group. Historically, binmail repeated this at 198 * each mail delivery. We no longer do this, assuming that if the 199 * ownership or permissions were changed there was a reason for doing 200 * so. 201 */ 202 bad: if (created) 203 (void)fchown(mbfd, pw->pw_uid, pw->pw_gid); 204 205 (void)fsync(mbfd); /* Don't wait for update. */ 206 (void)close(mbfd); /* Implicit unlock. */ 207 208 if (!rval) 209 notifybiff(biffmsg); 210 return(rval); 211 } 212 213 notifybiff(msg) 214 char *msg; 215 { 216 static struct sockaddr_in addr; 217 static int f = -1; 218 struct hostent *hp; 219 struct servent *sp; 220 int len; 221 222 if (!addr.sin_family) { 223 /* Be silent if biff service not available. */ 224 if (!(sp = getservbyname("biff", "udp"))) 225 return; 226 if (!(hp = gethostbyname("localhost"))) { 227 error(NOTFATAL, "localhost: %s.", strerror(errno)); 228 return; 229 } 230 addr.sin_family = hp->h_addrtype; 231 bcopy(hp->h_addr, &addr.sin_addr, hp->h_length); 232 addr.sin_port = sp->s_port; 233 } 234 if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { 235 error(NOTFATAL, "socket: %s.", strerror(errno)); 236 return; 237 } 238 len = strlen(msg) + 1; 239 if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof(addr)) 240 != len) 241 error(NOTFATAL, "sendto biff: %s.", strerror(errno)); 242 } 243 244 usage() 245 { 246 error(FATAL, "usage: mail.local [-f from] user ..."); 247 } 248 249 /* VARARGS */ 250 error(isfatal, fmt) 251 int isfatal; 252 char *fmt; 253 { 254 va_list ap; 255 256 va_start(ap, fmt); 257 vsyslog(LOG_ERR, fmt, ap); 258 va_end(ap); 259 if (isfatal) 260 exit(1); 261 } 262