1 1.11 sevan /* $NetBSD: inetcf.c,v 1.11 2018/01/23 21:06:26 sevan Exp $ */ 2 1.4 christos 3 1.1 cjs /* 4 1.1 cjs * Routines to parse an inetd.conf or tlid.conf file. This would be a great 5 1.1 cjs * job for a PERL script. 6 1.1 cjs * 7 1.1 cjs * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. 8 1.1 cjs */ 9 1.1 cjs 10 1.4 christos #include <sys/cdefs.h> 11 1.1 cjs #ifndef lint 12 1.4 christos #if 0 13 1.5 itojun static char sccsid[] = "@(#) inetcf.c 1.7 97/02/12 02:13:23"; 14 1.4 christos #else 15 1.11 sevan __RCSID("$NetBSD: inetcf.c,v 1.11 2018/01/23 21:06:26 sevan Exp $"); 16 1.4 christos #endif 17 1.1 cjs #endif 18 1.1 cjs 19 1.1 cjs #include <sys/types.h> 20 1.1 cjs #include <sys/stat.h> 21 1.1 cjs #include <stdio.h> 22 1.1 cjs #include <errno.h> 23 1.1 cjs #include <string.h> 24 1.3 cgd #include <stdlib.h> 25 1.1 cjs 26 1.1 cjs #include "tcpd.h" 27 1.1 cjs #include "inetcf.h" 28 1.4 christos #include "percent_m.h" 29 1.4 christos #include "scaffold.h" 30 1.4 christos 31 1.11 sevan static void inet_chk(char *, char *, char *, char *); 32 1.11 sevan static char *base_name(char *); 33 1.1 cjs 34 1.1 cjs /* 35 1.2 cjs * Programs that use libwrap directly are not in inetd.conf, and so must 36 1.2 cjs * be added here in a similar format. (We pretend we found them in 37 1.2 cjs * /etc/inetd.conf.) Each one is a set of three strings that correspond 38 1.2 cjs * to fields in /etc/inetd.conf: 39 1.2 cjs * protocol (field 3), path (field 6), arg0 (field 7) 40 1.2 cjs * The last entry should be a NULL. 41 1.2 cjs */ 42 1.2 cjs char *uses_libwrap[] = { 43 1.6 itojun "tcp", "/usr/sbin/sendmail", "sendmail", 44 1.6 itojun "tcp", "/usr/sbin/sshd", "sshd", 45 1.7 itojun "udp", "/usr/sbin/syslogd", "syslogd", 46 1.7 itojun "udp", "/usr/sbin/rpcbind", "rpcbind", 47 1.9 plunky NULL 48 1.2 cjs }; 49 1.2 cjs 50 1.2 cjs /* 51 1.1 cjs * Network configuration files may live in unusual places. Here are some 52 1.1 cjs * guesses. Shorter names follow longer ones. 53 1.1 cjs */ 54 1.1 cjs char *inet_files[] = { 55 1.1 cjs "/private/etc/inetd.conf", /* NEXT */ 56 1.1 cjs "/etc/inet/inetd.conf", /* SYSV4 */ 57 1.1 cjs "/usr/etc/inetd.conf", /* IRIX?? */ 58 1.1 cjs "/etc/inetd.conf", /* BSD */ 59 1.1 cjs "/etc/net/tlid.conf", /* SYSV4?? */ 60 1.1 cjs "/etc/saf/tlid.conf", /* SYSV4?? */ 61 1.1 cjs "/etc/tlid.conf", /* SYSV4?? */ 62 1.1 cjs 0, 63 1.1 cjs }; 64 1.1 cjs 65 1.1 cjs /* 66 1.1 cjs * Structure with everything we know about a service. 67 1.1 cjs */ 68 1.1 cjs struct inet_ent { 69 1.1 cjs struct inet_ent *next; 70 1.1 cjs int type; 71 1.1 cjs char name[1]; 72 1.1 cjs }; 73 1.1 cjs 74 1.1 cjs static struct inet_ent *inet_list = 0; 75 1.1 cjs 76 1.1 cjs static char whitespace[] = " \t\r\n"; 77 1.1 cjs 78 1.1 cjs /* inet_conf - read in and examine inetd.conf (or tlid.conf) entries */ 79 1.1 cjs 80 1.1 cjs char *inet_cfg(conf) 81 1.1 cjs char *conf; 82 1.1 cjs { 83 1.1 cjs char buf[BUFSIZ]; 84 1.4 christos FILE *fp = NULL; 85 1.2 cjs char **wrapped; 86 1.1 cjs char *service; 87 1.1 cjs char *protocol; 88 1.1 cjs char *user; 89 1.1 cjs char *path; 90 1.1 cjs char *arg0; 91 1.1 cjs char *arg1; 92 1.1 cjs struct tcpd_context saved_context; 93 1.1 cjs int i; 94 1.1 cjs struct stat st; 95 1.1 cjs 96 1.1 cjs saved_context = tcpd_context; 97 1.1 cjs 98 1.1 cjs /* 99 1.1 cjs * The inetd.conf (or tlid.conf) information is so useful that we insist 100 1.1 cjs * on its availability. When no file is given run a series of educated 101 1.1 cjs * guesses. 102 1.1 cjs */ 103 1.1 cjs if (conf != 0) { 104 1.1 cjs if ((fp = fopen(conf, "r")) == 0) { 105 1.10 christos fprintf(stderr, "open %s: %s\n", conf, strerror(errno)); 106 1.1 cjs exit(1); 107 1.1 cjs } 108 1.1 cjs } else { 109 1.1 cjs for (i = 0; inet_files[i] && (fp = fopen(inet_files[i], "r")) == 0; i++) 110 1.1 cjs /* void */ ; 111 1.1 cjs if (fp == 0) { 112 1.1 cjs fprintf(stderr, "Cannot find your inetd.conf or tlid.conf file.\n"); 113 1.1 cjs fprintf(stderr, "Please specify its location.\n"); 114 1.1 cjs exit(1); 115 1.1 cjs } 116 1.1 cjs conf = inet_files[i]; 117 1.1 cjs check_path(conf, &st); 118 1.1 cjs } 119 1.1 cjs 120 1.1 cjs /* 121 1.2 cjs * Process the list of programs that use libwrap directly. 122 1.2 cjs */ 123 1.2 cjs wrapped = uses_libwrap; 124 1.2 cjs while (*wrapped != NULL) { 125 1.2 cjs inet_chk(wrapped[0], wrapped[1], wrapped[2], ""); 126 1.2 cjs wrapped += 3; 127 1.2 cjs } 128 1.2 cjs 129 1.2 cjs /* 130 1.1 cjs * Process the file. After the 7.0 wrapper release it became clear that 131 1.1 cjs * there are many more inetd.conf formats than the 8 systems that I had 132 1.1 cjs * studied. EP/IX uses a two-line specification for rpc services; HP-UX 133 1.1 cjs * permits long lines to be broken with backslash-newline. 134 1.1 cjs */ 135 1.1 cjs tcpd_context.file = conf; 136 1.1 cjs tcpd_context.line = 0; 137 1.1 cjs while (xgets(buf, sizeof(buf), fp)) { 138 1.1 cjs service = strtok(buf, whitespace); /* service */ 139 1.1 cjs if (service == 0 || *service == '#') 140 1.1 cjs continue; 141 1.1 cjs if (STR_NE(service, "stream") && STR_NE(service, "dgram")) 142 1.1 cjs strtok((char *) 0, whitespace); /* endpoint */ 143 1.1 cjs protocol = strtok((char *) 0, whitespace); 144 1.1 cjs (void) strtok((char *) 0, whitespace); /* wait */ 145 1.1 cjs if ((user = strtok((char *) 0, whitespace)) == 0) 146 1.1 cjs continue; 147 1.1 cjs if (user[0] == '/') { /* user */ 148 1.1 cjs path = user; 149 1.1 cjs } else { /* path */ 150 1.1 cjs if ((path = strtok((char *) 0, whitespace)) == 0) 151 1.1 cjs continue; 152 1.1 cjs } 153 1.5 itojun if (path[0] == '?') /* IRIX optional service */ 154 1.5 itojun path++; 155 1.1 cjs if (STR_EQ(path, "internal")) 156 1.1 cjs continue; 157 1.1 cjs if (path[strspn(path, "-0123456789")] == 0) { 158 1.1 cjs 159 1.1 cjs /* 160 1.1 cjs * ConvexOS puts RPC version numbers before path names. Jukka 161 1.1 cjs * Ukkonen <ukkonen (at) csc.fi>. 162 1.1 cjs */ 163 1.1 cjs if ((path = strtok((char *) 0, whitespace)) == 0) 164 1.1 cjs continue; 165 1.1 cjs } 166 1.1 cjs if ((arg0 = strtok((char *) 0, whitespace)) == 0) { 167 1.1 cjs tcpd_warn("incomplete line"); 168 1.1 cjs continue; 169 1.1 cjs } 170 1.1 cjs if (arg0[strspn(arg0, "0123456789")] == 0) { 171 1.1 cjs 172 1.1 cjs /* 173 1.1 cjs * We're reading a tlid.conf file, the format is: 174 1.1 cjs * 175 1.1 cjs * ...stuff... path arg_count arguments mod_count modules 176 1.1 cjs */ 177 1.1 cjs if ((arg0 = strtok((char *) 0, whitespace)) == 0) { 178 1.1 cjs tcpd_warn("incomplete line"); 179 1.1 cjs continue; 180 1.1 cjs } 181 1.1 cjs } 182 1.1 cjs if ((arg1 = strtok((char *) 0, whitespace)) == 0) 183 1.1 cjs arg1 = ""; 184 1.1 cjs 185 1.1 cjs inet_chk(protocol, path, arg0, arg1); 186 1.1 cjs } 187 1.1 cjs fclose(fp); 188 1.1 cjs tcpd_context = saved_context; 189 1.1 cjs return (conf); 190 1.1 cjs } 191 1.1 cjs 192 1.1 cjs /* inet_chk - examine one inetd.conf (tlid.conf?) entry */ 193 1.1 cjs 194 1.11 sevan static void inet_chk(char *protocol, char *path, char *arg0, char *arg1) 195 1.1 cjs { 196 1.1 cjs char daemon[BUFSIZ]; 197 1.1 cjs struct stat st; 198 1.1 cjs int wrap_status = WR_MAYBE; 199 1.1 cjs char *base_name_path = base_name(path); 200 1.1 cjs char *tcpd_proc_name = (arg0[0] == '/' ? base_name(arg0) : arg0); 201 1.1 cjs 202 1.1 cjs /* 203 1.1 cjs * Always warn when the executable does not exist or when it is not 204 1.1 cjs * executable. 205 1.1 cjs */ 206 1.1 cjs if (check_path(path, &st) < 0) { 207 1.1 cjs tcpd_warn("%s: not found: %m", path); 208 1.1 cjs } else if ((st.st_mode & 0100) == 0) { 209 1.1 cjs tcpd_warn("%s: not executable", path); 210 1.1 cjs } 211 1.1 cjs 212 1.1 cjs /* 213 1.1 cjs * Cheat on the miscd tests, nobody uses it anymore. 214 1.1 cjs */ 215 1.1 cjs if (STR_EQ(base_name_path, "miscd")) { 216 1.1 cjs inet_set(arg0, WR_YES); 217 1.1 cjs return; 218 1.1 cjs } 219 1.1 cjs 220 1.1 cjs /* 221 1.1 cjs * While we are here... 222 1.1 cjs */ 223 1.1 cjs if (STR_EQ(tcpd_proc_name, "rexd") || STR_EQ(tcpd_proc_name, "rpc.rexd")) 224 1.1 cjs tcpd_warn("%s may be an insecure service", tcpd_proc_name); 225 1.1 cjs 226 1.1 cjs /* 227 1.1 cjs * The tcpd program gets most of the attention. 228 1.1 cjs */ 229 1.1 cjs if (STR_EQ(base_name_path, "tcpd")) { 230 1.1 cjs 231 1.1 cjs if (STR_EQ(tcpd_proc_name, "tcpd")) 232 1.1 cjs tcpd_warn("%s is recursively calling itself", tcpd_proc_name); 233 1.1 cjs 234 1.1 cjs wrap_status = WR_YES; 235 1.1 cjs 236 1.1 cjs /* 237 1.1 cjs * Check: some sites install the wrapper set-uid. 238 1.1 cjs */ 239 1.1 cjs if ((st.st_mode & 06000) != 0) 240 1.1 cjs tcpd_warn("%s: file is set-uid or set-gid", path); 241 1.1 cjs 242 1.1 cjs /* 243 1.1 cjs * Check: some sites insert tcpd in inetd.conf, instead of replacing 244 1.1 cjs * the daemon pathname. 245 1.1 cjs */ 246 1.1 cjs if (arg0[0] == '/' && STR_EQ(tcpd_proc_name, base_name(arg1))) 247 1.1 cjs tcpd_warn("%s inserted before %s", path, arg0); 248 1.1 cjs 249 1.1 cjs /* 250 1.1 cjs * Check: make sure files exist and are executable. On some systems 251 1.1 cjs * the network daemons are set-uid so we cannot complain. Note that 252 1.1 cjs * tcpd takes the basename only in case of absolute pathnames. 253 1.1 cjs */ 254 1.1 cjs if (arg0[0] == '/') { /* absolute path */ 255 1.1 cjs if (check_path(arg0, &st) < 0) { 256 1.1 cjs tcpd_warn("%s: not found: %m", arg0); 257 1.1 cjs } else if ((st.st_mode & 0100) == 0) { 258 1.1 cjs tcpd_warn("%s: not executable", arg0); 259 1.1 cjs } 260 1.1 cjs } else { /* look in REAL_DAEMON_DIR */ 261 1.8 itojun snprintf(daemon, sizeof(daemon), "%s/%s", REAL_DAEMON_DIR, arg0); 262 1.1 cjs if (check_path(daemon, &st) < 0) { 263 1.1 cjs tcpd_warn("%s: not found in %s: %m", 264 1.1 cjs arg0, REAL_DAEMON_DIR); 265 1.1 cjs } else if ((st.st_mode & 0100) == 0) { 266 1.1 cjs tcpd_warn("%s: not executable", daemon); 267 1.1 cjs } 268 1.1 cjs } 269 1.1 cjs 270 1.1 cjs } else { 271 1.1 cjs 272 1.1 cjs /* 273 1.1 cjs * No tcpd program found. Perhaps they used the "simple installation" 274 1.1 cjs * recipe. Look for a file with the same basename in REAL_DAEMON_DIR. 275 1.1 cjs * Draw some conservative conclusions when a distinct file is found. 276 1.1 cjs */ 277 1.8 itojun snprintf(daemon, sizeof(daemon), "%s/%s", REAL_DAEMON_DIR, arg0); 278 1.1 cjs if (STR_EQ(path, daemon)) { 279 1.1 cjs wrap_status = WR_NOT; 280 1.1 cjs } else if (check_path(daemon, &st) >= 0) { 281 1.1 cjs wrap_status = WR_MAYBE; 282 1.1 cjs } else if (errno == ENOENT) { 283 1.1 cjs wrap_status = WR_NOT; 284 1.1 cjs } else { 285 1.1 cjs tcpd_warn("%s: file lookup: %m", daemon); 286 1.1 cjs wrap_status = WR_MAYBE; 287 1.1 cjs } 288 1.1 cjs } 289 1.1 cjs 290 1.1 cjs /* 291 1.1 cjs * Alas, we cannot wrap rpc/tcp services. 292 1.1 cjs */ 293 1.1 cjs if (wrap_status == WR_YES && STR_EQ(protocol, "rpc/tcp")) 294 1.1 cjs tcpd_warn("%s: cannot wrap rpc/tcp services", tcpd_proc_name); 295 1.2 cjs 296 1.2 cjs /* NetBSD inetd wraps all programs */ 297 1.2 cjs if (! STR_EQ(protocol, "rpc/tcp")) 298 1.2 cjs wrap_status = WR_YES; 299 1.1 cjs 300 1.1 cjs inet_set(tcpd_proc_name, wrap_status); 301 1.1 cjs } 302 1.1 cjs 303 1.1 cjs /* inet_set - remember service status */ 304 1.1 cjs 305 1.11 sevan void inet_set(char *name, int type) 306 1.1 cjs { 307 1.1 cjs struct inet_ent *ip = 308 1.1 cjs (struct inet_ent *) malloc(sizeof(struct inet_ent) + strlen(name)); 309 1.1 cjs 310 1.1 cjs if (ip == 0) { 311 1.1 cjs fprintf(stderr, "out of memory\n"); 312 1.1 cjs exit(1); 313 1.1 cjs } 314 1.1 cjs ip->next = inet_list; 315 1.1 cjs strcpy(ip->name, name); 316 1.1 cjs ip->type = type; 317 1.1 cjs inet_list = ip; 318 1.1 cjs } 319 1.1 cjs 320 1.1 cjs /* inet_get - look up service status */ 321 1.1 cjs 322 1.11 sevan int inet_get(char *name) 323 1.1 cjs { 324 1.1 cjs struct inet_ent *ip; 325 1.1 cjs 326 1.1 cjs if (inet_list == 0) 327 1.1 cjs return (WR_MAYBE); 328 1.1 cjs 329 1.1 cjs for (ip = inet_list; ip; ip = ip->next) 330 1.1 cjs if (STR_EQ(ip->name, name)) 331 1.1 cjs return (ip->type); 332 1.1 cjs 333 1.1 cjs return (-1); 334 1.1 cjs } 335 1.1 cjs 336 1.1 cjs /* base_name - compute last pathname component */ 337 1.1 cjs 338 1.11 sevan static char *base_name(char *path) 339 1.1 cjs { 340 1.1 cjs char *cp; 341 1.1 cjs 342 1.1 cjs if ((cp = strrchr(path, '/')) != 0) 343 1.1 cjs path = cp + 1; 344 1.1 cjs return (path); 345 1.1 cjs } 346