1 1.14 sevan /* $NetBSD: pcnfsd_v2.c,v 1.14 2018/01/23 21:06:25 sevan Exp $ */ 2 1.2 gwr 3 1.1 jtc /* RE_SID: @(%)/usr/dosnfs/shades_SCCS/unix/pcnfsd/v2/src/SCCS/s.pcnfsd_v2.c 1.2 91/12/18 13:26:13 SMI */ 4 1.1 jtc /* 5 1.1 jtc **===================================================================== 6 1.1 jtc ** Copyright (c) 1986,1987,1988,1989,1990,1991 by Sun Microsystems, Inc. 7 1.1 jtc ** @(#)pcnfsd_v2.c 1.2 12/18/91 8 1.1 jtc **===================================================================== 9 1.1 jtc */ 10 1.1 jtc /* 11 1.1 jtc **===================================================================== 12 1.1 jtc ** I N C L U D E F I L E S E C T I O N * 13 1.1 jtc ** * 14 1.1 jtc ** If your port requires different include files, add a suitable * 15 1.1 jtc ** #define in the customization section, and make the inclusion or * 16 1.1 jtc ** exclusion of the files conditional on this. * 17 1.1 jtc **===================================================================== 18 1.1 jtc */ 19 1.1 jtc 20 1.1 jtc #include <sys/file.h> 21 1.6 lukem #include <sys/ioctl.h> 22 1.1 jtc #include <sys/stat.h> 23 1.6 lukem 24 1.6 lukem #include <grp.h> 25 1.1 jtc #include <netdb.h> 26 1.6 lukem #include <pwd.h> 27 1.6 lukem #include <signal.h> 28 1.6 lukem #include <stdio.h> 29 1.6 lukem #include <stdlib.h> 30 1.1 jtc #include <string.h> 31 1.6 lukem #include <unistd.h> 32 1.1 jtc 33 1.1 jtc #ifdef USE_YP 34 1.1 jtc #include <rpcsvc/ypclnt.h> 35 1.1 jtc #endif 36 1.1 jtc 37 1.1 jtc #ifndef SYSV 38 1.1 jtc #include <sys/wait.h> 39 1.1 jtc #endif 40 1.1 jtc 41 1.1 jtc #ifdef ISC_2_0 42 1.1 jtc #include <sys/fcntl.h> 43 1.1 jtc #endif 44 1.1 jtc 45 1.1 jtc #ifdef SHADOW_SUPPORT 46 1.1 jtc #include <shadow.h> 47 1.1 jtc #endif 48 1.1 jtc 49 1.6 lukem #include "common.h" 50 1.6 lukem #include "pcnfsd.h" 51 1.6 lukem #include "extern.h" 52 1.1 jtc 53 1.1 jtc /* 54 1.1 jtc **===================================================================== 55 1.4 gwr ** C O D E S E C T I O N * 56 1.4 gwr **===================================================================== 57 1.1 jtc */ 58 1.1 jtc 59 1.1 jtc 60 1.1 jtc static char no_comment[] = "No comment"; 61 1.1 jtc static char not_supported[] = "Not supported"; 62 1.1 jtc static char pcnfsd_version[] = "@(#)pcnfsd_v2.c 1.2 - rpc.pcnfsd V2.0 (c) 1991 Sun Technology Enterprises, Inc."; 63 1.1 jtc 64 1.1 jtc /*ARGSUSED*/ 65 1.6 lukem void * 66 1.14 sevan pcnfsd2_null_2_svc(void *arg, struct svc_req *req) 67 1.6 lukem { 68 1.6 lukem static char dummy; 69 1.6 lukem return ((void *) &dummy); 70 1.6 lukem } 71 1.6 lukem 72 1.6 lukem v2_auth_results * 73 1.14 sevan pcnfsd2_auth_2_svc(v2_auth_args *arg, struct svc_req *req) 74 1.6 lukem { 75 1.6 lukem static v2_auth_results r; 76 1.6 lukem 77 1.6 lukem char uname[32]; 78 1.6 lukem char pw[64]; 79 1.6 lukem int c1, c2; 80 1.6 lukem struct passwd *p; 81 1.6 lukem static u_int extra_gids[EXTRAGIDLEN]; 82 1.6 lukem static char home[256]; 83 1.1 jtc #ifdef USE_YP 84 1.6 lukem char *yphome; 85 1.6 lukem char *cp; 86 1.6 lukem #endif /* USE_YP */ 87 1.1 jtc 88 1.1 jtc 89 1.1 jtc r.stat = AUTH_RES_FAIL; /* assume failure */ 90 1.6 lukem r.uid = (int) -2; 91 1.6 lukem r.gid = (int) -2; 92 1.1 jtc r.cm = &no_comment[0]; 93 1.1 jtc r.gids.gids_len = 0; 94 1.1 jtc r.gids.gids_val = &extra_gids[0]; 95 1.1 jtc home[0] = '\0'; 96 1.1 jtc r.home = &home[0]; 97 1.1 jtc r.def_umask = umask(0); 98 1.6 lukem (void) umask(r.def_umask); /* or use 022 */ 99 1.1 jtc 100 1.1 jtc scramble(arg->id, uname); 101 1.1 jtc scramble(arg->pw, pw); 102 1.1 jtc 103 1.1 jtc #ifdef USER_CACHE 104 1.6 lukem if (check_cache(uname, pw, &r.uid, &r.gid)) { 105 1.6 lukem r.stat = AUTH_RES_OK; 106 1.1 jtc #ifdef WTMP 107 1.1 jtc wlogin(uname, req); 108 1.1 jtc #endif 109 1.6 lukem fillin_extra_groups 110 1.6 lukem (uname, r.gid, &r.gids.gids_len, extra_gids); 111 1.1 jtc #ifdef USE_YP 112 1.1 jtc yphome = find_entry(uname, "auto.home"); 113 1.6 lukem if (yphome) { 114 1.9 itojun strlcpy(home, yphome, sizeof(home)); 115 1.1 jtc free(yphome); 116 1.1 jtc cp = strchr(home, ':'); 117 1.1 jtc cp++; 118 1.1 jtc cp = strchr(cp, ':'); 119 1.6 lukem if (cp) 120 1.1 jtc *cp = '/'; 121 1.1 jtc } 122 1.1 jtc #endif 123 1.6 lukem return (&r); 124 1.6 lukem } 125 1.1 jtc #endif 126 1.1 jtc 127 1.1 jtc p = get_password(uname); 128 1.11 plunky if (p == NULL) 129 1.6 lukem return (&r); 130 1.1 jtc 131 1.1 jtc c1 = strlen(pw); 132 1.1 jtc c2 = strlen(p->pw_passwd); 133 1.1 jtc if ((c1 && !c2) || (c2 && !c1) || 134 1.6 lukem (strcmp(p->pw_passwd, crypt(pw, p->pw_passwd)))) { 135 1.6 lukem return (&r); 136 1.6 lukem } 137 1.1 jtc r.stat = AUTH_RES_OK; 138 1.1 jtc r.uid = p->pw_uid; 139 1.1 jtc r.gid = p->pw_gid; 140 1.1 jtc #ifdef WTMP 141 1.1 jtc wlogin(uname, req); 142 1.1 jtc #endif 143 1.6 lukem fillin_extra_groups(uname, r.gid, &r.gids.gids_len, extra_gids); 144 1.1 jtc 145 1.1 jtc #ifdef USE_YP 146 1.1 jtc yphome = find_entry(uname, "auto.home"); 147 1.6 lukem if (yphome) { 148 1.9 itojun strlcpy(home, yphome, sizeof(home)); 149 1.1 jtc free(yphome); 150 1.1 jtc cp = strchr(home, ':'); 151 1.1 jtc cp++; 152 1.1 jtc cp = strchr(cp, ':'); 153 1.6 lukem if (cp) 154 1.1 jtc *cp = '/'; 155 1.1 jtc } 156 1.1 jtc #endif 157 1.1 jtc 158 1.1 jtc #ifdef USER_CACHE 159 1.1 jtc add_cache_entry(p); 160 1.1 jtc #endif 161 1.1 jtc 162 1.6 lukem return (&r); 163 1.1 jtc 164 1.1 jtc } 165 1.1 jtc 166 1.6 lukem v2_pr_init_results * 167 1.14 sevan pcnfsd2_pr_init_2_svc(v2_pr_init_args *arg, struct svc_req *req) 168 1.1 jtc { 169 1.6 lukem static v2_pr_init_results res; 170 1.1 jtc 171 1.6 lukem res.stat = 172 1.6 lukem (pirstat) pr_init(arg->system, arg->pn, &res.dir); 173 1.1 jtc res.cm = &no_comment[0]; 174 1.1 jtc 175 1.1 jtc 176 1.6 lukem return (&res); 177 1.1 jtc } 178 1.1 jtc 179 1.6 lukem v2_pr_start_results * 180 1.14 sevan pcnfsd2_pr_start_2_svc(v2_pr_start_args *arg, struct svc_req *req) 181 1.1 jtc { 182 1.6 lukem static v2_pr_start_results res; 183 1.1 jtc 184 1.1 jtc res.stat = 185 1.6 lukem (psrstat) pr_start2(arg->system, arg->pn, arg->user, 186 1.6 lukem arg->file, arg->opts, &res.id); 187 1.1 jtc res.cm = &no_comment[0]; 188 1.1 jtc 189 1.6 lukem return (&res); 190 1.1 jtc } 191 1.1 jtc /*ARGSUSED*/ 192 1.6 lukem v2_pr_list_results * 193 1.14 sevan pcnfsd2_pr_list_2_svc(void *arg, struct svc_req *req) 194 1.1 jtc { 195 1.6 lukem static v2_pr_list_results res; 196 1.1 jtc 197 1.6 lukem if (printers == NULL) 198 1.6 lukem (void) build_pr_list(); 199 1.1 jtc res.cm = &no_comment[0]; 200 1.1 jtc res.printers = printers; 201 1.1 jtc 202 1.6 lukem return (&res); 203 1.1 jtc } 204 1.1 jtc 205 1.6 lukem v2_pr_queue_results * 206 1.14 sevan pcnfsd2_pr_queue_2_svc(v2_pr_queue_args *arg, struct svc_req *req) 207 1.1 jtc { 208 1.6 lukem static v2_pr_queue_results res; 209 1.1 jtc 210 1.1 jtc res.stat = build_pr_queue(arg->pn, arg->user, 211 1.6 lukem arg->just_mine, &res.qlen, &res.qshown); 212 1.1 jtc res.cm = &no_comment[0]; 213 1.1 jtc res.just_yours = arg->just_mine; 214 1.1 jtc res.jobs = queue; 215 1.1 jtc 216 1.6 lukem 217 1.6 lukem return (&res); 218 1.1 jtc } 219 1.1 jtc 220 1.6 lukem v2_pr_status_results * 221 1.14 sevan pcnfsd2_pr_status_2_svc(v2_pr_status_args *arg, struct svc_req *req) 222 1.1 jtc { 223 1.6 lukem static v2_pr_status_results res; 224 1.6 lukem static char status[128]; 225 1.1 jtc 226 1.1 jtc res.stat = get_pr_status(arg->pn, &res.avail, &res.printing, 227 1.13 apb &res.qlen, &res.needs_operator, &status[0], sizeof(status)); 228 1.6 lukem res.status = &status[0]; 229 1.1 jtc res.cm = &no_comment[0]; 230 1.1 jtc 231 1.6 lukem return (&res); 232 1.1 jtc } 233 1.1 jtc 234 1.6 lukem v2_pr_cancel_results * 235 1.14 sevan pcnfsd2_pr_cancel_2_svc(v2_pr_cancel_args *arg, struct svc_req *req) 236 1.1 jtc { 237 1.6 lukem static v2_pr_cancel_results res; 238 1.1 jtc 239 1.1 jtc res.stat = pr_cancel(arg->pn, arg->user, arg->id); 240 1.1 jtc res.cm = &no_comment[0]; 241 1.1 jtc 242 1.6 lukem return (&res); 243 1.1 jtc } 244 1.1 jtc /*ARGSUSED*/ 245 1.6 lukem v2_pr_requeue_results * 246 1.14 sevan pcnfsd2_pr_requeue_2_svc(v2_pr_requeue_args *arg, struct svc_req *req) 247 1.1 jtc { 248 1.6 lukem static v2_pr_requeue_results res; 249 1.1 jtc res.stat = PC_RES_FAIL; 250 1.1 jtc res.cm = ¬_supported[0]; 251 1.1 jtc 252 1.6 lukem return (&res); 253 1.1 jtc } 254 1.1 jtc /*ARGSUSED*/ 255 1.6 lukem v2_pr_hold_results * 256 1.14 sevan pcnfsd2_pr_hold_2_svc(v2_pr_hold_args *arg, struct svc_req *req) 257 1.1 jtc { 258 1.6 lukem static v2_pr_hold_results res; 259 1.1 jtc 260 1.1 jtc res.stat = PC_RES_FAIL; 261 1.1 jtc res.cm = ¬_supported[0]; 262 1.1 jtc 263 1.6 lukem return (&res); 264 1.1 jtc } 265 1.1 jtc /*ARGSUSED*/ 266 1.6 lukem v2_pr_release_results * 267 1.14 sevan pcnfsd2_pr_release_2_svc(v2_pr_release_args *arg, struct svc_req *req) 268 1.1 jtc { 269 1.6 lukem static v2_pr_release_results res; 270 1.1 jtc 271 1.1 jtc res.stat = PC_RES_FAIL; 272 1.1 jtc res.cm = ¬_supported[0]; 273 1.1 jtc 274 1.6 lukem return (&res); 275 1.1 jtc } 276 1.1 jtc /*ARGSUSED*/ 277 1.6 lukem v2_pr_admin_results * 278 1.14 sevan pcnfsd2_pr_admin_2_svc(v2_pr_admin_args *arg, struct svc_req *req) 279 1.1 jtc { 280 1.6 lukem static v2_pr_admin_results res; 281 1.1 jtc /* 282 1.1 jtc ** The default action for admin is to fail. 283 1.1 jtc ** If someone wishes to implement an administration 284 1.1 jtc ** mechanism, and isn't worried about the security 285 1.1 jtc ** holes, go right ahead. 286 1.1 jtc */ 287 1.1 jtc 288 1.1 jtc res.cm = ¬_supported[0]; 289 1.1 jtc res.stat = PI_RES_FAIL; 290 1.1 jtc 291 1.6 lukem return (&res); 292 1.1 jtc } 293 1.1 jtc 294 1.1 jtc void 295 1.14 sevan free_mapreq_results(mapreq_res p) 296 1.1 jtc { 297 1.6 lukem if (p->mapreq_next) 298 1.6 lukem free_mapreq_results(p->mapreq_next); /* recurse */ 299 1.6 lukem if (p->name) 300 1.6 lukem (void) free(p->name); 301 1.6 lukem (void) free(p); 302 1.1 jtc return; 303 1.1 jtc } 304 1.1 jtc 305 1.14 sevan static char *my_strdup(const char *); 306 1.6 lukem 307 1.1 jtc static char * 308 1.14 sevan my_strdup(const char *s) 309 1.1 jtc { 310 1.12 joerg size_t len; 311 1.6 lukem char *r; 312 1.12 joerg len = strlen(s); 313 1.12 joerg r = (char *) grab(len + 1); 314 1.12 joerg memcpy(r, s, len + 1); 315 1.6 lukem return (r); 316 1.1 jtc } 317 1.1 jtc 318 1.6 lukem v2_mapid_results * 319 1.14 sevan pcnfsd2_mapid_2_svc(v2_mapid_args *arg, struct svc_req *req) 320 1.1 jtc { 321 1.6 lukem static v2_mapid_results res; 322 1.6 lukem struct passwd *p_passwd; 323 1.6 lukem struct group *p_group; 324 1.1 jtc 325 1.6 lukem mapreq_arg a; 326 1.6 lukem mapreq_res next_r; 327 1.6 lukem mapreq_res last_r = NULL; 328 1.1 jtc 329 1.1 jtc 330 1.6 lukem if (res.res_list) { 331 1.1 jtc free_mapreq_results(res.res_list); 332 1.1 jtc res.res_list = NULL; 333 1.1 jtc } 334 1.1 jtc a = arg->req_list; 335 1.6 lukem while (a) { 336 1.1 jtc next_r = (struct mapreq_res_item *) 337 1.6 lukem grab(sizeof(struct mapreq_res_item)); 338 1.1 jtc next_r->stat = MAP_RES_UNKNOWN; 339 1.1 jtc next_r->req = a->req; 340 1.1 jtc next_r->id = a->id; 341 1.1 jtc next_r->name = NULL; 342 1.1 jtc next_r->mapreq_next = NULL; 343 1.1 jtc 344 1.6 lukem if (last_r == NULL) 345 1.1 jtc res.res_list = next_r; 346 1.1 jtc else 347 1.1 jtc last_r->mapreq_next = next_r; 348 1.1 jtc last_r = next_r; 349 1.6 lukem switch (a->req) { 350 1.1 jtc case MAP_REQ_UID: 351 1.6 lukem p_passwd = getpwuid((uid_t) a->id); 352 1.6 lukem if (p_passwd) { 353 1.1 jtc next_r->name = my_strdup(p_passwd->pw_name); 354 1.1 jtc next_r->stat = MAP_RES_OK; 355 1.1 jtc } 356 1.1 jtc break; 357 1.1 jtc case MAP_REQ_GID: 358 1.6 lukem p_group = getgrgid((gid_t) a->id); 359 1.6 lukem if (p_group) { 360 1.1 jtc next_r->name = my_strdup(p_group->gr_name); 361 1.1 jtc next_r->stat = MAP_RES_OK; 362 1.1 jtc } 363 1.1 jtc break; 364 1.1 jtc case MAP_REQ_UNAME: 365 1.1 jtc next_r->name = my_strdup(a->name); 366 1.1 jtc p_passwd = getpwnam(a->name); 367 1.6 lukem if (p_passwd) { 368 1.1 jtc next_r->id = p_passwd->pw_uid; 369 1.1 jtc next_r->stat = MAP_RES_OK; 370 1.1 jtc } 371 1.1 jtc break; 372 1.1 jtc case MAP_REQ_GNAME: 373 1.1 jtc next_r->name = my_strdup(a->name); 374 1.1 jtc p_group = getgrnam(a->name); 375 1.6 lukem if (p_group) { 376 1.1 jtc next_r->id = p_group->gr_gid; 377 1.1 jtc next_r->stat = MAP_RES_OK; 378 1.1 jtc } 379 1.1 jtc break; 380 1.1 jtc } 381 1.6 lukem if (next_r->name == NULL) 382 1.1 jtc next_r->name = my_strdup(""); 383 1.1 jtc a = a->mapreq_next; 384 1.1 jtc } 385 1.1 jtc 386 1.1 jtc res.cm = &no_comment[0]; 387 1.1 jtc 388 1.6 lukem return (&res); 389 1.1 jtc } 390 1.1 jtc 391 1.6 lukem 392 1.1 jtc /*ARGSUSED*/ 393 1.6 lukem v2_alert_results * 394 1.14 sevan pcnfsd2_alert_2_svc(v2_alert_args *arg, struct svc_req *req) 395 1.1 jtc { 396 1.6 lukem static v2_alert_results res; 397 1.1 jtc 398 1.1 jtc res.stat = ALERT_RES_FAIL; 399 1.1 jtc res.cm = ¬_supported[0]; 400 1.1 jtc 401 1.6 lukem return (&res); 402 1.1 jtc } 403 1.1 jtc /*ARGSUSED*/ 404 1.6 lukem v2_info_results * 405 1.14 sevan pcnfsd2_info_2_svc(v2_info_args *arg, struct svc_req *req) 406 1.6 lukem { 407 1.6 lukem static v2_info_results res; 408 1.6 lukem static int facilities[FACILITIESMAX]; 409 1.6 lukem static int onetime = 1; 410 1.1 jtc 411 1.1 jtc #define UNSUPPORTED -1 412 1.1 jtc #define QUICK 100 413 1.1 jtc #define SLOW 2000 414 1.1 jtc 415 1.6 lukem if (onetime) { 416 1.1 jtc onetime = 0; 417 1.1 jtc facilities[PCNFSD2_NULL] = QUICK; 418 1.1 jtc facilities[PCNFSD2_INFO] = QUICK; 419 1.1 jtc facilities[PCNFSD2_PR_INIT] = QUICK; 420 1.1 jtc facilities[PCNFSD2_PR_START] = SLOW; 421 1.6 lukem facilities[PCNFSD2_PR_LIST] = QUICK; /* except first time */ 422 1.1 jtc facilities[PCNFSD2_PR_QUEUE] = SLOW; 423 1.1 jtc facilities[PCNFSD2_PR_STATUS] = SLOW; 424 1.1 jtc facilities[PCNFSD2_PR_CANCEL] = SLOW; 425 1.1 jtc facilities[PCNFSD2_PR_ADMIN] = UNSUPPORTED; 426 1.1 jtc facilities[PCNFSD2_PR_REQUEUE] = UNSUPPORTED; 427 1.1 jtc facilities[PCNFSD2_PR_HOLD] = UNSUPPORTED; 428 1.1 jtc facilities[PCNFSD2_PR_RELEASE] = UNSUPPORTED; 429 1.1 jtc facilities[PCNFSD2_MAPID] = QUICK; 430 1.1 jtc facilities[PCNFSD2_AUTH] = QUICK; 431 1.1 jtc facilities[PCNFSD2_ALERT] = QUICK; 432 1.1 jtc } 433 1.6 lukem res.facilities.facilities_len = PCNFSD2_ALERT + 1; 434 1.1 jtc res.facilities.facilities_val = facilities; 435 1.6 lukem 436 1.1 jtc res.vers = &pcnfsd_version[0]; 437 1.1 jtc res.cm = &no_comment[0]; 438 1.1 jtc 439 1.6 lukem return (&res); 440 1.1 jtc } 441 1.1 jtc 442 1.1 jtc 443 1.1 jtc 444 1.1 jtc void 445 1.14 sevan fillin_extra_groups(char *uname, gid_t main_gid, int *len, gid_t extra_gids[EXTRAGIDLEN]) 446 1.6 lukem { 447 1.6 lukem struct group *grp; 448 1.8 mycroft __aconst char *__aconst *members; 449 1.6 lukem int n = 0; 450 1.1 jtc 451 1.1 jtc setgrent(); 452 1.1 jtc 453 1.6 lukem while (n < EXTRAGIDLEN) { 454 1.1 jtc grp = getgrent(); 455 1.6 lukem if (grp == NULL) 456 1.1 jtc break; 457 1.6 lukem if (grp->gr_gid == main_gid) 458 1.1 jtc continue; 459 1.6 lukem for (members = grp->gr_mem; members && *members; members++) { 460 1.6 lukem if (!strcmp(*members, uname)) { 461 1.1 jtc extra_gids[n++] = grp->gr_gid; 462 1.1 jtc break; 463 1.1 jtc } 464 1.1 jtc } 465 1.1 jtc } 466 1.1 jtc endgrent(); 467 1.1 jtc *len = n; 468 1.1 jtc } 469 1.1 jtc 470 1.1 jtc #ifdef USE_YP 471 1.1 jtc /* the following is from rpcsvc/yp_prot.h */ 472 1.1 jtc #define YPMAXDOMAIN 64 473 1.6 lukem 474 1.1 jtc /* 475 1.1 jtc * find_entry returns NULL on any error (printing a message) and 476 1.1 jtc * otherwise returns a pointer to the malloc'd result. The caller 477 1.1 jtc * is responsible for free()ing the result string. 478 1.1 jtc */ 479 1.6 lukem char * 480 1.14 sevan find_entry(const char *key, const char *map) 481 1.1 jtc { 482 1.6 lukem int err; 483 1.6 lukem char *val = NULL; 484 1.6 lukem char *cp; 485 1.6 lukem int len = 0; 486 1.6 lukem static char domain[YPMAXDOMAIN + 1]; 487 1.1 jtc 488 1.6 lukem if (getdomainname(domain, YPMAXDOMAIN)) { 489 1.1 jtc msg_out("rpc.pcnfsd: getdomainname failed"); 490 1.6 lukem return (NULL); 491 1.1 jtc } 492 1.6 lukem if ((err = yp_bind(domain)) != 0) { 493 1.3 gwr #ifdef DEBUG 494 1.1 jtc msg_out("rpc.pcnfsd: yp_bind failed"); 495 1.3 gwr #endif 496 1.6 lukem return (NULL); 497 1.1 jtc } 498 1.1 jtc err = yp_match(domain, map, key, strlen(key), &val, &len); 499 1.1 jtc 500 1.1 jtc if (err) { 501 1.1 jtc msg_out("rpc.pcnfsd: yp_match failed"); 502 1.5 lukem if (val) 503 1.5 lukem free(val); 504 1.6 lukem return (NULL); 505 1.1 jtc } 506 1.6 lukem if ((cp = strchr(val, '\n')) != NULL) 507 1.6 lukem *cp = '\0'; /* in case we get an extra NL at the end */ 508 1.6 lukem return (val); 509 1.1 jtc } 510 1.1 jtc #endif 511