| 1 | static char sccsid[] = "@(#)net.c 4.2 (Berkeley) %G%"; |
| 2 | |
| 3 | /* sccs id variable */ |
| 4 | static char *net_sid = "@(#)net.c 1.8"; |
| 5 | |
| 6 | # include "defs.h" |
| 7 | /* must be setuid root */ |
| 8 | /* |
| 9 | net - -b -c cmd -f -i file -l name -mmach -n -o file -p passwd |
| 10 | -r file -s file -u uid -w -x -y -z command |
| 11 | |
| 12 | - take from standard input |
| 13 | -b never send anything back |
| 14 | -c cmd think of this as a "cmd" * |
| 15 | -f force prompting of user name and password |
| 16 | -i file remote stdin * |
| 17 | -l name remote login name |
| 18 | -m Mach remote machine |
| 19 | -n do not write back anything, always mail them back |
| 20 | -o file remote stdout & stderr * |
| 21 | -p pass remote password |
| 22 | -q quiet option, send back only if rcode !=0 or if there is stdout |
| 23 | -r file local response file |
| 24 | -s file local stdin file * |
| 25 | |
| 26 | (super users only, always skip login/passwd check:) |
| 27 | -u uid net queue files should be owned by uid (16 bits) |
| 28 | -w this is a write/mail response cmd * |
| 29 | -x this is being forwarded through us to another machine * |
| 30 | -y skip login/password check * |
| 31 | -z this is a response file being returned * |
| 32 | |
| 33 | * = not documented in net(NEW) |
| 34 | |
| 35 | */ |
| 36 | /* |
| 37 | code option reason |
| 38 | q normal request |
| 39 | w -w message to be written back |
| 40 | -x being forwarded through us |
| 41 | y -y simply skips login check (used by netlpr) |
| 42 | s -z normal response |
| 43 | */ |
| 44 | /* global variables */ |
| 45 | struct userinfo status; |
| 46 | |
| 47 | /* local variables */ |
| 48 | static char dfname[]= DFNAME; |
| 49 | |
| 50 | main(argc, argv) |
| 51 | char **argv; { |
| 52 | register int i; |
| 53 | int outerror(),uid; |
| 54 | char localin[FNS], skey[30]; |
| 55 | char buf[BUFSIZ], suid[10]; |
| 56 | char sin =0, zopt = 0, wopt = 0, yopt = 0, xopt = 0; |
| 57 | char *s,**sargv; |
| 58 | long cnt = 0L, maxfile = MAXFILELARGE; |
| 59 | FILE *file, *temp, *rfile; |
| 60 | struct utmp *putmp; |
| 61 | struct stat statbuf; |
| 62 | struct header hd; |
| 63 | |
| 64 | debugflg = DBV; |
| 65 | hd.hd_scmdact[0] = hd.hd_srespfile[0] = hd.hd_soutfile[0] = 0; |
| 66 | hd.hd_sinfile[0] = hd.hd_scmdvirt[0] = hd.hd_sttyname[0] = 0; |
| 67 | localin[0] = 0; |
| 68 | suid[0] = 0; |
| 69 | sargv = argv; |
| 70 | |
| 71 | if(isatty(0)) strcat(hd.hd_sttyname,ttyname(0)); |
| 72 | else if(isatty(2)) strcat(hd.hd_sttyname,ttyname(2)); |
| 73 | remote = 0; |
| 74 | if (signal(SIGHUP, SIG_IGN) != SIG_IGN) |
| 75 | signal(SIGHUP, outerror); |
| 76 | if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) |
| 77 | signal(SIGQUIT, outerror); |
| 78 | if (signal(SIGINT, SIG_IGN) != SIG_IGN) |
| 79 | signal(SIGINT, outerror); |
| 80 | if (signal(SIGTERM, SIG_IGN) != SIG_IGN) |
| 81 | signal(SIGTERM, outerror); |
| 82 | |
| 83 | while(argc > 1 && argv[1][0] == '-'){ |
| 84 | argc--; argv++; |
| 85 | switch(argv[0][1]){ |
| 86 | case 0: |
| 87 | sin++; |
| 88 | break; |
| 89 | case 'b': |
| 90 | status.nonotify++; |
| 91 | break; |
| 92 | case 'c': |
| 93 | harg(hd.hd_scmdvirt); |
| 94 | break; |
| 95 | case 'f': |
| 96 | status.force++; |
| 97 | break; |
| 98 | case 'i': |
| 99 | harg(hd.hd_sinfile); |
| 100 | break; |
| 101 | case 'l': |
| 102 | harg(status.login); |
| 103 | break; |
| 104 | case 'm': |
| 105 | harg(buf); |
| 106 | remote = lookup(buf); |
| 107 | if(remote == 0){ |
| 108 | fprintf(stderr,"Unknown machine %s\n",buf); |
| 109 | exit(EX_NOHOST); |
| 110 | } |
| 111 | break; |
| 112 | case 'n': |
| 113 | status.nowrite++; |
| 114 | break; |
| 115 | case 'o': |
| 116 | harg(hd.hd_soutfile); |
| 117 | break; |
| 118 | case 'p': |
| 119 | harg(status.mpasswd); |
| 120 | if(status.mpasswd[0] == 0) |
| 121 | strcpy(status.mpasswd,"\n\n"); |
| 122 | break; |
| 123 | case 'q': |
| 124 | status.quiet++; |
| 125 | break; |
| 126 | case 'r': |
| 127 | harg(buf); |
| 128 | addir(hd.hd_srespfile,buf); |
| 129 | break; |
| 130 | case 's': |
| 131 | harg(localin); |
| 132 | break; |
| 133 | case 'u': |
| 134 | harg(suid); |
| 135 | break; |
| 136 | case 'w': |
| 137 | wopt++; |
| 138 | break; |
| 139 | case 'x': |
| 140 | xopt++; |
| 141 | break; |
| 142 | case 'y': |
| 143 | yopt++; |
| 144 | break; |
| 145 | case 'z': |
| 146 | zopt++; |
| 147 | break; |
| 148 | default: |
| 149 | fprintf(stderr,"Unknown option %s\n",argv[0]); |
| 150 | break; |
| 151 | } |
| 152 | } |
| 153 | while(argc > 1){ |
| 154 | argc--; argv++; |
| 155 | strcat(hd.hd_scmdact,argv[0]); |
| 156 | strcat(hd.hd_scmdact," "); |
| 157 | } |
| 158 | sargv[1] = 0; /* so ps won't show passwd ??? */ |
| 159 | hd.hd_uidfrom = uid = getuid(); |
| 160 | hd.hd_gidfrom = getgid(); |
| 161 | hd.hd_code = 'q'; |
| 162 | if(zopt || wopt || yopt || xopt || suid[0] != 0){ |
| 163 | /* check z or w or y or x option permission */ |
| 164 | # ifndef TESTING |
| 165 | /* check effective user id ?? */ |
| 166 | if (uid != SUPERUSER && uid != NUID) { |
| 167 | fprintf(stderr, "Error: Not super-user\n"); |
| 168 | fprintf(stderr,"zopt %d wopt %d yopt %d xopt %d suid[0] %s\n", zopt, wopt, yopt, xopt, suid); |
| 169 | fprintf(stderr,"uid %d\n", uid); |
| 170 | debugflg = 1; |
| 171 | printhd(&hd); |
| 172 | outerror(EX_UNAVAILABLE); |
| 173 | } |
| 174 | # endif |
| 175 | hd.hd_code = zopt? 's': 'w'; |
| 176 | hd.hd_code = yopt? 'y': hd.hd_code; |
| 177 | if (status.mpasswd[0] == 0) /* no passwd required */ |
| 178 | strcpy(status.mpasswd, "\n"); |
| 179 | debug("zopt %d wopt %d yopt %d xopt %d suid[0] %s\n", zopt, wopt, yopt, xopt, suid); |
| 180 | debug("uid %d\n", uid); |
| 181 | if(xopt) |
| 182 | setuid(SUPERUSER); |
| 183 | } |
| 184 | #ifdef CRN |
| 185 | strcpy( status.jobno, MAGICCRN ); /* default (invalid) crn */ |
| 186 | #else |
| 187 | strcpy( status.jobno, "XYZZ"); /* default (invalid) crn */ |
| 188 | #endif |
| 189 | |
| 190 | if(hd.hd_code == 'q' && !xopt){ |
| 191 | /* read passwd file, get status.localname & crn */ |
| 192 | passwdent(); |
| 193 | } |
| 194 | |
| 195 | /* sets remote,status.login,status.force,status.mpasswd, |
| 196 | status.nonotify, status.nowrite */ |
| 197 | /* may read passwd file if getenv(HOME) reads it */ |
| 198 | commandfile(); |
| 199 | if(status.force)status.login[0] = status.mpasswd[0] = 0; |
| 200 | |
| 201 | /* look up login name and passwd in the environment */ |
| 202 | envloginpasswd(remote,status.login,status.mpasswd); |
| 203 | |
| 204 | |
| 205 | if(remote == 0)remote = getremote(local); |
| 206 | # ifndef TESTING |
| 207 | if(remote == local){ |
| 208 | fprintf(stderr,"Request sent to local machine - doesn't make sense\n"); |
| 209 | /* outerror(); */ |
| 210 | } |
| 211 | # endif |
| 212 | strcat(status.defcmd," "); |
| 213 | if(strlen(hd.hd_scmdact) == 0)strcpy(hd.hd_scmdact,status.defcmd); |
| 214 | hd.hd_scmdact[strlen(hd.hd_scmdact)-1] = 0; |
| 215 | do { |
| 216 | mktemp(dfname); /* make until unique!! */ |
| 217 | } while(stat(dfname,&statbuf) >= 0); |
| 218 | /* determine through machine */ |
| 219 | i = gothru(local,remote); |
| 220 | if(i == 0){ |
| 221 | s = longname(remote); |
| 222 | if(s != 0)fprintf(stderr,"No path to %s machine.\n",s); |
| 223 | else fprintf(stderr,"Unknown machine\n"); |
| 224 | outerror(EX_NOHOST); |
| 225 | } |
| 226 | dfname[strlen(dfname)-11] = i; /* set directory */ |
| 227 | dfname[strlen(dfname)-7] = i; /* set file (unused) */ |
| 228 | /* check to see if data files are directories */ |
| 229 | if(isdirectory(hd.hd_srespfile) || isdirectory(hd.hd_sinfile) || isdirectory(hd.hd_soutfile)){ |
| 230 | fprintf(stderr,"%s is a directory, must be a file\n", |
| 231 | isdirectory(hd.hd_srespfile) ? hd.hd_srespfile : |
| 232 | isdirectory(hd.hd_sinfile) ? hd.hd_sinfile : |
| 233 | hd.hd_soutfile); |
| 234 | outerror(EX_USAGE); |
| 235 | } |
| 236 | if(suid[0] != 0)uid = atoi(suid); |
| 237 | if(hd.hd_srespfile[0]){ |
| 238 | if(strcmp(hd.hd_srespfile,"/dev/tty") == 0){ |
| 239 | fprintf(stderr,"Can't have /dev/tty as response file.\n"); |
| 240 | outerror(EX_USAGE); |
| 241 | } |
| 242 | if(stat(hd.hd_srespfile,&statbuf) == -1){ |
| 243 | strcpy(buf,hd.hd_srespfile); |
| 244 | s = &buf[0]; |
| 245 | s = s + strlen(buf) - 1; |
| 246 | while(*s != '/' && s > &(buf[0]))s--; |
| 247 | *s = 0; |
| 248 | debug("chkdir %s",buf); |
| 249 | if(strlen(buf) == 0)strcpy(buf,"."); |
| 250 | if(access(buf,2) == -1){ |
| 251 | perror(buf); |
| 252 | outerror(EX_USAGE); |
| 253 | } |
| 254 | if((rfile=fopen(hd.hd_srespfile,"w")) == NULL){ |
| 255 | perror(hd.hd_srespfile); |
| 256 | outerror(EX_USAGE); |
| 257 | } |
| 258 | chmod(hd.hd_srespfile,0600); |
| 259 | fclose(rfile); |
| 260 | mchown(hd.hd_srespfile,uid,hd.hd_gidfrom); |
| 261 | } |
| 262 | else if(access(hd.hd_srespfile,2) == -1){ |
| 263 | perror(hd.hd_srespfile); |
| 264 | outerror(EX_USAGE); |
| 265 | } |
| 266 | else if(getsize(&statbuf) != 0L){ |
| 267 | fprintf(stderr,"%s must have 0-length or not exist\n", |
| 268 | hd.hd_srespfile); |
| 269 | outerror(EX_USAGE); |
| 270 | } |
| 271 | } |
| 272 | /* go ahead and prompt for login name and passwd, if neccessary, |
| 273 | as long as the X option has not been specified */ |
| 274 | if(hd.hd_code == 'q' && !xopt)promptlogin(remote); |
| 275 | |
| 276 | /* at this point, we create the dfa... file */ |
| 277 | file = fopen(dfname,"w"); |
| 278 | if(file == NULL){ |
| 279 | perror(dfname); |
| 280 | outerror(EX_OSERR); |
| 281 | } |
| 282 | chmod(dfname,0600); |
| 283 | mchown(dfname,uid,getgid()); |
| 284 | if(xopt)goto stickit; |
| 285 | if(status.mpasswd[0] == '\n') |
| 286 | status.mpasswd[0] = 0; |
| 287 | if(status.mpasswd[0] == 0 && hd.hd_code == 'q' && |
| 288 | strcmp(status.login,"network") != 0){ |
| 289 | fprintf(stderr,"Zero-length password not allowed\n"); |
| 290 | outerror(EX_USAGE); |
| 291 | } |
| 292 | if(hd.hd_code == 'q' && (streql(status.login,"root") == 0 || |
| 293 | streql(status.login,"ruut") == 0)){ |
| 294 | fprintf(stderr,"Can't login as root through the network\n"); |
| 295 | outerror(EX_USAGE); |
| 296 | } |
| 297 | makeuukey(skey,status.login,remote); |
| 298 | nbsencrypt(status.mpasswd,skey,hd.hd_sencpasswd); |
| 299 | enmask(status.mpasswd); |
| 300 | hd.hd_lttytime = 0; |
| 301 | if(hd.hd_sttyname[0] && status.nowrite == 0){ |
| 302 | putmp = getutmp(hd.hd_sttyname); |
| 303 | if(putmp != NULL) hd.hd_lttytime = putmp->ut_time; |
| 304 | } |
| 305 | /* |
| 306 | debug("p:%s:\n",status.mpasswd); |
| 307 | */ |
| 308 | /* write the header info onto 'file' */ |
| 309 | hd.hd_mchto = remote; |
| 310 | hd.hd_mesgid.msg_mch = hd.hd_mchfrom = local; |
| 311 | hd.hd_vmajor = VMAJOR; |
| 312 | hd.hd_vminor = VMINOR; |
| 313 | strcpy(hd.hd_snto,status.login); |
| 314 | strcpy(hd.hd_snfrom,status.localname); |
| 315 | strcpy(hd.hd_spasswd,status.mpasswd); |
| 316 | strcpy(hd.hd_ijobno, status.jobno ); |
| 317 | hd.hd_mesgid.msg_ltime = hd.hd_ltimesent = gettime(); |
| 318 | hd.hd_fquiet = status.quiet; |
| 319 | hd.hd_fnonotify = status.nonotify; |
| 320 | hd.hd_mesgid.msg_pid = getpid(); |
| 321 | hd.hd_fcompressed = 0; |
| 322 | /* handle account pairs, accounts which do not require |
| 323 | a passwd if you are logged in on the same one here */ |
| 324 | hd.hd_facctpair = fisacctpair(&hd); |
| 325 | |
| 326 | writehdfd(&hd,file); |
| 327 | printhd(&hd); |
| 328 | stickit: |
| 329 | if(sin) |
| 330 | while((i = fread(buf,1,BUFSIZ,stdin)) > 0){ |
| 331 | if(fwrite(buf,1,i,file) != i){ |
| 332 | perror("net queue file"); |
| 333 | outerror(EX_OSFILE); |
| 334 | } |
| 335 | if((cnt += i) > maxfile)goto toobig; |
| 336 | if(feof(stdin))break; |
| 337 | } |
| 338 | else if(localin[0]){ |
| 339 | if(access(localin,4) == -1){ |
| 340 | perror(localin); |
| 341 | outerror(EX_OSFILE); |
| 342 | } |
| 343 | temp = fopen(localin,"r"); |
| 344 | if(temp == NULL){ |
| 345 | perror(localin); |
| 346 | outerror(EX_OSFILE); |
| 347 | } |
| 348 | while((i = fread(buf,1,BUFSIZ,temp)) > 0){ |
| 349 | if((cnt += i) > maxfile)goto toobig; |
| 350 | if(fwrite(buf,1,i,file) != i){ |
| 351 | perror("net queue file"); |
| 352 | outerror(EX_OSFILE); |
| 353 | } |
| 354 | } |
| 355 | fclose(temp); |
| 356 | } |
| 357 | fclose(file); |
| 358 | chmod(dfname,0400); |
| 359 | dfname[strlen(dfname)-9] = 'c'; |
| 360 | file = fopen(dfname,"w"); |
| 361 | chmod(dfname,0400); |
| 362 | fclose(file); |
| 363 | mchown(dfname,uid,getgid()); |
| 364 | exit(EX_OK); |
| 365 | toobig: |
| 366 | fprintf(stderr,"No more than %ld bytes can be sent\n",maxfile); |
| 367 | outerror(EX_USAGE); /* no return */ |
| 368 | } |
| 369 | /* |
| 370 | called if there is an error, makes sure that the files created |
| 371 | are deleted and the terminal is reset to echo |
| 372 | */ |
| 373 | outerror(ret){ |
| 374 | register int i; |
| 375 | struct sgttyb stt; |
| 376 | signal(SIGHUP,SIG_IGN); signal(SIGINT,SIG_IGN); |
| 377 | signal(SIGQUIT,SIG_IGN); signal(SIGTERM,SIG_IGN); |
| 378 | unlink(dfname); |
| 379 | i = strlen(dfname) - 9; |
| 380 | dfname[i] = (dfname[i] == 'c' ? 'd' : 'c'); |
| 381 | unlink(dfname); |
| 382 | if(gtty(0,&stt) >= 0){ |
| 383 | stt.sg_flags |= ECHO; |
| 384 | stty(0,&stt); |
| 385 | } |
| 386 | exit(ret); |
| 387 | } |
| 388 | enmask(s) |
| 389 | register char *s; { |
| 390 | while(*s){ |
| 391 | *s &= 0177; /* strip quote bites */ |
| 392 | *s++ ^= 040; /* invert upper-lower */ |
| 393 | } |
| 394 | } |
| 395 | addir(s,t) |
| 396 | register char *s, *t; { |
| 397 | if(t[0] == '/')strcpy(s,t); |
| 398 | else { |
| 399 | gwd(s); |
| 400 | strcat(s,t); |
| 401 | } |
| 402 | } |
| 403 | |
| 404 | /* returns true if phd is an account pair, false otherwise */ |
| 405 | fisacctpair(phd) |
| 406 | register struct header *phd; |
| 407 | { |
| 408 | return(0); |
| 409 | } |
| 410 | |
| 411 | |
| 412 | |
| 413 | static struct stat x; |
| 414 | static struct direct y; |
| 415 | static int off = -1; |
| 416 | |
| 417 | |
| 418 | /* these three routines gwd, cat, ckroot and |
| 419 | data structures x, y, off, do a pwd to string name */ |
| 420 | #ifdef V6 |
| 421 | static FILE *file; |
| 422 | |
| 423 | gwd(name) |
| 424 | register char *name; { |
| 425 | *name = 0; |
| 426 | for(;;){ |
| 427 | stat(".",&x); |
| 428 | if((file = fopen("..","r")) == NULL)break; |
| 429 | do { |
| 430 | if(fread(&y,1,sizeof y,file) != sizeof y)break; |
| 431 | } while(y.d_ino != x.st_ino); |
| 432 | fclose(file); |
| 433 | if(y.d_ino == ROOTINO){ |
| 434 | ckroot(name); |
| 435 | break; |
| 436 | } |
| 437 | if(cat(name))break; |
| 438 | chdir(".."); |
| 439 | } |
| 440 | chdir(name); |
| 441 | } |
| 442 | ckroot(name) |
| 443 | char *name; { |
| 444 | register int i; |
| 445 | if(stat(y.d_name,&x) < 0)return; |
| 446 | i = x.st_dev; |
| 447 | if(chdir("/") < 0)return; |
| 448 | if((file = fopen("/","r")) == NULL)return; |
| 449 | do { |
| 450 | if(fread(&y,1,sizeof y,file) != sizeof y)return; |
| 451 | if(y.d_ino == 0)continue; |
| 452 | if(stat(y.d_name,&x) < 0)return; |
| 453 | } while(x.st_dev!=i || (x.st_mode&S_IFMT)!=S_IFDIR); |
| 454 | if(strcmp(y.d_name,".") != 0 && strcmp(y.d_name,"..") != 0) |
| 455 | if(cat(name))return; |
| 456 | i = strlen(name); |
| 457 | name[i+1] = 0; |
| 458 | while(--i >= 0)name[i + 1] = name[i]; |
| 459 | name[0] = '/'; |
| 460 | return; |
| 461 | } |
| 462 | #else |
| 463 | static DIR *file; |
| 464 | static struct stat xx; |
| 465 | |
| 466 | gwd(name) |
| 467 | register char *name; { |
| 468 | int rdev, rino; |
| 469 | register int i; |
| 470 | register struct direct *dp; |
| 471 | |
| 472 | *name = 0; |
| 473 | stat("/", &x); |
| 474 | rdev = x.st_dev; |
| 475 | rino = x.st_ino; |
| 476 | for (;;) { |
| 477 | stat(".", &x); |
| 478 | if (x.st_ino == rino && x.st_dev == rdev) |
| 479 | break; |
| 480 | if ((file = opendir("..")) == NULL) |
| 481 | break; |
| 482 | fstat(file->dd_fd, &xx); |
| 483 | chdir(".."); |
| 484 | if (x.st_dev == xx.st_dev) { |
| 485 | if (x.st_ino == xx.st_ino) |
| 486 | break; |
| 487 | do |
| 488 | if ((dp = readdir(file)) == NULL) |
| 489 | break; |
| 490 | while (dp->d_ino != x.st_ino); |
| 491 | } |
| 492 | else do { |
| 493 | if ((dp = readdir(file)) == NULL) |
| 494 | break; |
| 495 | stat(dp->d_name, &xx); |
| 496 | } while (xx.st_ino != x.st_ino || xx.st_dev != x.st_dev); |
| 497 | blkcpy(dp, &y, DIRSIZ(dp)); |
| 498 | closedir(file); |
| 499 | if (cat(name)) |
| 500 | break; |
| 501 | } |
| 502 | i = strlen(name); |
| 503 | name[i+1] = 0; |
| 504 | while (--i >= 0) name[i+1] = name[i]; |
| 505 | name[0] = '/'; |
| 506 | } |
| 507 | #endif |
| 508 | |
| 509 | cat(name) |
| 510 | register char *name; { /* return 1 to exit */ |
| 511 | register int i,j; |
| 512 | i = -1; |
| 513 | while(y.d_name[++i] != 0); |
| 514 | if((off+i+2) > 511)return(1); |
| 515 | for(j = off +1; j >= 0; --j)name[j+i+1] = name[j]; |
| 516 | off = i + off + 1; |
| 517 | name[i] = '/'; |
| 518 | for(--i; i>= 0; --i)name[i] = y.d_name[i]; |
| 519 | return(0); |
| 520 | } |
| 521 | |
| 522 | |
| 523 | /* |
| 524 | this function takes a file name and tells whether it is a |
| 525 | directory or on. Returns 1 if so, 0 otherwise. |
| 526 | null strings etc. return 0. |
| 527 | */ |
| 528 | isdirectory(fn) |
| 529 | char *fn; |
| 530 | { |
| 531 | int i,ret=0; |
| 532 | if(fn == NULL || *fn == 0)return(0); |
| 533 | i = strlen(fn); |
| 534 | if(i == 1){ |
| 535 | if(strcmp(fn,".") == 0)ret = 1; |
| 536 | if(strcmp(fn,"/") == 0)ret = 1; |
| 537 | } |
| 538 | else if(i == 2){ |
| 539 | if(strcmp(fn,"..") == 0)ret = 1; |
| 540 | if(strcmp(fn,"/.") == 0)ret = 1; |
| 541 | } |
| 542 | else { |
| 543 | if(strcmp(fn+i-2,"/.") == 0)ret = 1; |
| 544 | if(strcmp(fn+i-3,"/..") == 0)ret = 1; |
| 545 | } |
| 546 | return(ret); |
| 547 | } |
| 548 | |
| 549 | blkcpy(from, to, size) |
| 550 | register *from, *to; |
| 551 | register int size; |
| 552 | { |
| 553 | while (size-- > 0) |
| 554 | *to++ = *from++; |
| 555 | } |