1 /* $OpenBSD: makemap.c,v 1.74 2021/01/27 07:20:27 deraadt Exp $ */ 2 3 /* 4 * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org> 5 * Copyright (c) 2008-2009 Jacek Masiulaniec <jacekm@dobremiasto.net> 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 #include <sys/types.h> 21 #include <sys/stat.h> 22 #include <sys/tree.h> 23 #include <sys/queue.h> 24 #include <sys/socket.h> 25 26 #include <ctype.h> 27 #include <db.h> 28 #include <err.h> 29 #include <errno.h> 30 #include <event.h> 31 #include <fcntl.h> 32 #include <imsg.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <syslog.h> 37 #include <unistd.h> 38 #include <limits.h> 39 #include <util.h> 40 41 #include "smtpd.h" 42 #include "log.h" 43 44 #define PATH_ALIASES "/etc/mail/aliases" 45 46 static void usage(void); 47 static int parse_map(DB *, int *, char *); 48 static int parse_entry(DB *, int *, char *, size_t, size_t); 49 static int parse_mapentry(DB *, int *, char *, size_t, size_t); 50 static int parse_setentry(DB *, int *, char *, size_t, size_t); 51 static int make_plain(DBT *, char *); 52 static int make_aliases(DBT *, char *); 53 static char *conf_aliases(char *); 54 static int dump_db(const char *, DBTYPE); 55 56 char *source; 57 static int mode; 58 59 enum output_type { 60 T_PLAIN, 61 T_ALIASES, 62 T_SET 63 } type; 64 65 /* 66 * Stub functions so that makemap compiles using minimum object files. 67 */ 68 int 69 fork_proc_backend(const char *backend, const char *conf, const char *procname) 70 { 71 return (-1); 72 } 73 74 int 75 makemap(int prog_mode, int argc, char *argv[]) 76 { 77 struct stat sb; 78 char dbname[PATH_MAX]; 79 DB *db; 80 const char *opts; 81 char *conf, *oflag = NULL; 82 int ch, dbputs = 0, Uflag = 0; 83 DBTYPE dbtype = DB_HASH; 84 char *p; 85 gid_t gid; 86 int fd = -1; 87 88 gid = getgid(); 89 if (setresgid(gid, gid, gid) == -1) 90 err(1, "setresgid"); 91 92 if ((env = config_default()) == NULL) 93 err(1, NULL); 94 95 log_init(1, LOG_MAIL); 96 97 mode = prog_mode; 98 conf = CONF_FILE; 99 type = T_PLAIN; 100 opts = "b:C:d:ho:O:t:U"; 101 if (mode == P_NEWALIASES) 102 opts = "f:h"; 103 104 while ((ch = getopt(argc, argv, opts)) != -1) { 105 switch (ch) { 106 case 'b': 107 if (optarg && strcmp(optarg, "i") == 0) 108 mode = P_NEWALIASES; 109 break; 110 case 'C': 111 break; /* for compatibility */ 112 case 'd': 113 if (strcmp(optarg, "hash") == 0) 114 dbtype = DB_HASH; 115 else if (strcmp(optarg, "btree") == 0) 116 dbtype = DB_BTREE; 117 else 118 errx(1, "unsupported DB type '%s'", optarg); 119 break; 120 case 'f': 121 conf = optarg; 122 break; 123 case 'o': 124 oflag = optarg; 125 break; 126 case 'O': 127 if (strncmp(optarg, "AliasFile=", 10) != 0) 128 break; 129 type = T_ALIASES; 130 p = strchr(optarg, '='); 131 source = ++p; 132 break; 133 case 't': 134 if (strcmp(optarg, "aliases") == 0) 135 type = T_ALIASES; 136 else if (strcmp(optarg, "set") == 0) 137 type = T_SET; 138 else 139 errx(1, "unsupported type '%s'", optarg); 140 break; 141 case 'U': 142 Uflag = 1; 143 break; 144 default: 145 usage(); 146 } 147 } 148 argc -= optind; 149 argv += optind; 150 151 /* sendmail-compat makemap ... re-execute using proper interface */ 152 if (argc == 2) { 153 if (oflag) 154 usage(); 155 156 p = strstr(argv[1], ".db"); 157 if (p == NULL || strcmp(p, ".db") != 0) { 158 if (!bsnprintf(dbname, sizeof dbname, "%s.db", 159 argv[1])) 160 errx(1, "database name too long"); 161 } 162 else { 163 if (strlcpy(dbname, argv[1], sizeof dbname) 164 >= sizeof dbname) 165 errx(1, "database name too long"); 166 } 167 168 execl(PATH_MAKEMAP, "makemap", "-d", argv[0], "-o", dbname, 169 "-", (char *)NULL); 170 err(1, "execl"); 171 } 172 173 if (mode == P_NEWALIASES) { 174 if (geteuid()) 175 errx(1, "need root privileges"); 176 if (argc != 0) 177 usage(); 178 type = T_ALIASES; 179 if (source == NULL) 180 source = conf_aliases(conf); 181 } else { 182 if (argc != 1) 183 usage(); 184 source = argv[0]; 185 } 186 187 if (Uflag) 188 return dump_db(source, dbtype); 189 190 if (oflag == NULL && asprintf(&oflag, "%s.db", source) == -1) 191 err(1, "asprintf"); 192 193 if (strcmp(source, "-") != 0) 194 if (stat(source, &sb) == -1) 195 err(1, "stat: %s", source); 196 197 if (!bsnprintf(dbname, sizeof(dbname), "%s.XXXXXXXXXXX", oflag)) 198 errx(1, "path too long"); 199 if ((fd = mkstemp(dbname)) == -1) 200 err(1, "mkstemp"); 201 202 db = dbopen(dbname, O_TRUNC|O_RDWR, 0644, dbtype, NULL); 203 if (db == NULL) { 204 warn("dbopen: %s", dbname); 205 goto bad; 206 } 207 208 if (strcmp(source, "-") != 0) 209 if (fchmod(db->fd(db), sb.st_mode) == -1 || 210 fchown(db->fd(db), sb.st_uid, sb.st_gid) == -1) { 211 warn("couldn't carry ownership and perms to %s", 212 dbname); 213 goto bad; 214 } 215 216 if (!parse_map(db, &dbputs, source)) 217 goto bad; 218 219 if (db->close(db) == -1) { 220 warn("dbclose: %s", dbname); 221 goto bad; 222 } 223 224 /* force to disk before renaming over an existing file */ 225 if (fsync(fd) == -1) { 226 warn("fsync: %s", dbname); 227 goto bad; 228 } 229 if (close(fd) == -1) { 230 fd = -1; 231 warn("close: %s", dbname); 232 goto bad; 233 } 234 fd = -1; 235 236 if (rename(dbname, oflag) == -1) { 237 warn("rename"); 238 goto bad; 239 } 240 241 if (mode == P_NEWALIASES) 242 printf("%s: %d aliases\n", source, dbputs); 243 else if (dbputs == 0) 244 warnx("warning: empty map created: %s", oflag); 245 246 return 0; 247 bad: 248 if (fd != -1) 249 close(fd); 250 unlink(dbname); 251 return 1; 252 } 253 254 static int 255 parse_map(DB *db, int *dbputs, char *filename) 256 { 257 FILE *fp; 258 char *line; 259 size_t len; 260 size_t lineno = 0; 261 262 if (strcmp(filename, "-") == 0) 263 fp = fdopen(0, "r"); 264 else 265 fp = fopen(filename, "r"); 266 if (fp == NULL) { 267 warn("%s", filename); 268 return 0; 269 } 270 271 if (!isatty(fileno(fp)) && flock(fileno(fp), LOCK_SH|LOCK_NB) == -1) { 272 if (errno == EWOULDBLOCK) 273 warnx("%s is locked", filename); 274 else 275 warn("%s: flock", filename); 276 fclose(fp); 277 return 0; 278 } 279 280 while ((line = fparseln(fp, &len, &lineno, 281 NULL, FPARSELN_UNESCCOMM)) != NULL) { 282 if (!parse_entry(db, dbputs, line, len, lineno)) { 283 free(line); 284 fclose(fp); 285 return 0; 286 } 287 free(line); 288 } 289 290 fclose(fp); 291 return 1; 292 } 293 294 static int 295 parse_entry(DB *db, int *dbputs, char *line, size_t len, size_t lineno) 296 { 297 switch (type) { 298 case T_PLAIN: 299 case T_ALIASES: 300 return parse_mapentry(db, dbputs, line, len, lineno); 301 case T_SET: 302 return parse_setentry(db, dbputs, line, len, lineno); 303 } 304 return 0; 305 } 306 307 static int 308 parse_mapentry(DB *db, int *dbputs, char *line, size_t len, size_t lineno) 309 { 310 DBT key; 311 DBT val; 312 char *keyp; 313 char *valp; 314 315 keyp = line; 316 while (isspace((unsigned char)*keyp)) 317 keyp++; 318 if (*keyp == '\0') 319 return 1; 320 321 valp = keyp; 322 strsep(&valp, " \t:"); 323 if (valp == NULL || valp == keyp) 324 goto bad; 325 while (*valp == ':' || isspace((unsigned char)*valp)) 326 valp++; 327 if (*valp == '\0') 328 goto bad; 329 330 /* Check for dups. */ 331 key.data = keyp; 332 key.size = strlen(keyp) + 1; 333 334 xlowercase(key.data, key.data, strlen(key.data) + 1); 335 if (db->get(db, &key, &val, 0) == 0) { 336 warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp); 337 return 0; 338 } 339 340 if (type == T_PLAIN) { 341 if (!make_plain(&val, valp)) 342 goto bad; 343 } 344 else if (type == T_ALIASES) { 345 if (!make_aliases(&val, valp)) 346 goto bad; 347 } 348 349 if (db->put(db, &key, &val, 0) == -1) { 350 warn("dbput"); 351 return 0; 352 } 353 354 (*dbputs)++; 355 356 free(val.data); 357 358 return 1; 359 360 bad: 361 warnx("%s:%zd: invalid entry", source, lineno); 362 return 0; 363 } 364 365 static int 366 parse_setentry(DB *db, int *dbputs, char *line, size_t len, size_t lineno) 367 { 368 DBT key; 369 DBT val; 370 char *keyp; 371 372 keyp = line; 373 while (isspace((unsigned char)*keyp)) 374 keyp++; 375 if (*keyp == '\0') 376 return 1; 377 378 val.data = "<set>"; 379 val.size = strlen(val.data) + 1; 380 381 /* Check for dups. */ 382 key.data = keyp; 383 key.size = strlen(keyp) + 1; 384 xlowercase(key.data, key.data, strlen(key.data) + 1); 385 if (db->get(db, &key, &val, 0) == 0) { 386 warnx("%s:%zd: duplicate entry for %s", source, lineno, keyp); 387 return 0; 388 } 389 390 if (db->put(db, &key, &val, 0) == -1) { 391 warn("dbput"); 392 return 0; 393 } 394 395 (*dbputs)++; 396 397 return 1; 398 } 399 400 static int 401 make_plain(DBT *val, char *text) 402 { 403 val->data = xstrdup(text); 404 val->size = strlen(text) + 1; 405 406 return (val->size); 407 } 408 409 static int 410 make_aliases(DBT *val, char *text) 411 { 412 struct expandnode xn; 413 char *subrcpt; 414 char *origtext; 415 416 val->data = NULL; 417 val->size = 0; 418 419 origtext = xstrdup(text); 420 421 while ((subrcpt = strsep(&text, ",")) != NULL) { 422 /* subrcpt: strip initial and trailing whitespace. */ 423 subrcpt = strip(subrcpt); 424 if (*subrcpt == '\0') 425 goto error; 426 427 if (!text_to_expandnode(&xn, subrcpt)) 428 goto error; 429 } 430 431 val->data = origtext; 432 val->size = strlen(origtext) + 1; 433 return (val->size); 434 435 error: 436 free(origtext); 437 438 return 0; 439 } 440 441 static char * 442 conf_aliases(char *cfgpath) 443 { 444 struct table *table; 445 char *path; 446 char *p; 447 448 if (parse_config(env, cfgpath, 0)) 449 exit(1); 450 451 table = table_find(env, "aliases"); 452 if (table == NULL) 453 return (PATH_ALIASES); 454 455 path = xstrdup(table->t_config); 456 p = strstr(path, ".db"); 457 if (p == NULL || strcmp(p, ".db") != 0) { 458 return (path); 459 } 460 *p = '\0'; 461 return (path); 462 } 463 464 static int 465 dump_db(const char *dbname, DBTYPE dbtype) 466 { 467 DB *db; 468 DBT key, val; 469 char *keystr, *valstr; 470 int r; 471 472 db = dbopen(dbname, O_RDONLY, 0644, dbtype, NULL); 473 if (db == NULL) 474 err(1, "dbopen: %s", dbname); 475 476 for (r = db->seq(db, &key, &val, R_FIRST); r == 0; 477 r = db->seq(db, &key, &val, R_NEXT)) { 478 keystr = key.data; 479 valstr = val.data; 480 if (keystr[key.size - 1] == '\0') 481 key.size--; 482 if (valstr[val.size - 1] == '\0') 483 val.size--; 484 printf("%.*s\t%.*s\n", (int)key.size, keystr, 485 (int)val.size, valstr); 486 } 487 if (r == -1) 488 err(1, "db->seq: %s", dbname); 489 490 if (db->close(db) == -1) 491 err(1, "dbclose: %s", dbname); 492 493 return 0; 494 } 495 496 static void 497 usage(void) 498 { 499 if (mode == P_NEWALIASES) 500 fprintf(stderr, "usage: newaliases [-f file]\n"); 501 else 502 fprintf(stderr, "usage: makemap [-U] [-d dbtype] [-o dbfile] " 503 "[-t type] file\n"); 504 exit(1); 505 } 506