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