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