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