|
|
1.1 ! root 1: char *userv = "User Interface 4E(059), 29 Jan 88"; ! 2: ! 3: /* C K U U S R -- "User Interface" for Unix Kermit (Part 1) */ ! 4: ! 5: /* ! 6: 4E, support for Apollo Aegis, Data General added, July 87. ! 7: */ ! 8: /* ! 9: Author: Frank da Cruz (SY.FDC@CU20B), ! 10: Columbia University Center for Computing Activities, January 1985. ! 11: Copyright (C) 1985, Trustees of Columbia University in the City of New York. ! 12: Permission is granted to any individual or institution to use, copy, or ! 13: redistribute this software so long as it is not sold for profit, provided this ! 14: copyright notice is retained. ! 15: */ ! 16: ! 17: /* ! 18: The ckuusr module contains the terminal input and output functions for Unix ! 19: Kermit. It includes a simple Unix-style command line parser as well as ! 20: an interactive prompting keyword command parser. It depends on the existence ! 21: of Unix facilities like fopen, fgets, feof, (f)printf, argv/argc, etc. Other ! 22: functions that are likely to vary among Unix implementations -- like setting ! 23: terminal modes or interrupts -- are invoked via calls to functions that are ! 24: defined in the system-dependent modules, ck?[ft]io.c. ! 25: ! 26: The command line parser processes any arguments found on the command line, ! 27: as passed to main() via argv/argc. The interactive parser uses the facilities ! 28: of the cmd package (developed for this program, but usable by any program). ! 29: ! 30: Any command parser may be substituted for this one. The only requirements ! 31: for the Kermit command parser are these: ! 32: ! 33: 1. Set parameters via global variables like duplex, speed, ttname, etc. ! 34: See ckmain.c for the declarations and descriptions of these variables. ! 35: ! 36: 2. If a command can be executed without the use of Kermit protocol, then ! 37: execute the command directly and set the variable sstate to 0. Examples ! 38: include 'set' commands, local directory listings, the 'connect' command. ! 39: ! 40: 3. If a command requires the Kermit protocol, set the following variables: ! 41: ! 42: sstate string data ! 43: 'x' (enter server mode) (none) ! 44: 'r' (send a 'get' command) cmarg, cmarg2 ! 45: 'v' (enter receive mode) cmarg2 ! 46: 'g' (send a generic command) cmarg ! 47: 's' (send files) nfils, cmarg & cmarg2 OR cmlist ! 48: 'c' (send a remote host command) cmarg ! 49: ! 50: cmlist is an array of pointers to strings. ! 51: cmarg, cmarg2 are pointers to strings. ! 52: nfils is an integer. ! 53: ! 54: cmarg can be a filename string (possibly wild), or ! 55: a pointer to a prefabricated generic command string, or ! 56: a pointer to a host command string. ! 57: cmarg2 is the name to send a single file under, or ! 58: the name under which to store an incoming file; must not be wild. ! 59: cmlist is a list of nonwild filenames, such as passed via argv. ! 60: nfils is an integer, interpreted as follows: ! 61: -1: argument string is in cmarg, and should be expanded internally. ! 62: 0: stdin. ! 63: >0: number of files to send, from cmlist. ! 64: ! 65: The screen() function is used to update the screen during file transfer. ! 66: The tlog() function maintains a transaction log. ! 67: The debug() function maintains a debugging log. ! 68: The intmsg() and chkint() functions provide the user i/o for interrupting ! 69: file transfers. ! 70: */ ! 71: ! 72: /* Includes */ ! 73: ! 74: #include "ckcdeb.h" ! 75: #include <stdio.h> ! 76: #include <ctype.h> ! 77: #ifndef AMIGA ! 78: #include <signal.h> ! 79: #endif ! 80: #include "ckcker.h" ! 81: #include "ckucmd.h" ! 82: #include "ckuusr.h" ! 83: ! 84: #ifdef datageneral ! 85: #define fgets(stringbuf,max,fd) dg_fgets(stringbuf,max,fd) ! 86: #define fork() vfork() ! 87: /* DG version 3.21 of C has bugs in the following routines, since they ! 88: * depend on /etc/passwd. In the context where the routines are used, ! 89: * we don't need them anyway. ! 90: */ ! 91: #define getgid() -1 ! 92: #define getuid() -1 ! 93: #define geteuid() -1 ! 94: #endif ! 95: ! 96: /* External Kermit Variables, see ckmain.c for description. */ ! 97: ! 98: extern int size, rpsiz, urpsiz, speed, local, ! 99: server, displa, binary, parity, deblog, escape, xargc, flow, ! 100: turn, duplex, nfils, ckxech, pktlog, seslog, tralog, stdouf, ! 101: turnch, dfloc, keep, maxrps, warn, quiet, cnflg, tlevel; ! 102: ! 103: extern char *versio, *protv, *ckxv, *ckzv, *fnsv, *connv, *dftty, *cmdv; ! 104: extern char *dialv, *loginv; ! 105: extern char *ckxsys, *ckzsys, *cmarg, *cmarg2, **xargv, **cmlist; ! 106: extern char *DIRCMD, *PWDCMD, cmerrp[]; ! 107: extern CHAR sstate, ttname[]; ! 108: char *strcpy(), *getenv(); ! 109: #ifdef AMIGA ! 110: char *getcwd(); ! 111: #endif ! 112: ! 113: /* Declarations from cmd package */ ! 114: ! 115: extern char cmdbuf[]; /* Command buffer */ ! 116: ! 117: /* Declarations from ck?fio.c module */ ! 118: ! 119: extern char *SPACMD, *zhome(); /* Space command, home directory. */ ! 120: extern int backgrd; /* Kermit executing in background */ ! 121: ! 122: /* The background flag is set by ckutio.c (via conint() ) to note whether */ ! 123: /* this kermit is executing in background ('&' on shell command line). */ ! 124: ! 125: ! 126: /* Variables and symbols local to this module */ ! 127: ! 128: char line[CMDBL+10], *lp; /* Character buffer for anything */ ! 129: char debfil[50]; /* Debugging log file name */ ! 130: char pktfil[50]; /* Packet log file name */ ! 131: char sesfil[50]; /* Session log file name */ ! 132: char trafil[50]; /* Transaction log file name */ ! 133: ! 134: int n, /* General purpose int */ ! 135: cflg, /* Command-line connect cmd given */ ! 136: action, /* Action selected on command line*/ ! 137: repars, /* Reparse needed */ ! 138: cwdf = 0; /* CWD has been done */ ! 139: ! 140: #define MAXTAKE 20 /* Maximum nesting of TAKE files */ ! 141: FILE *tfile[MAXTAKE]; /* File pointers for TAKE command */ ! 142: ! 143: char *homdir; /* Pointer to home directory string */ ! 144: char cmdstr[100]; /* Place to build generic command */ ! 145: ! 146: /* C M D L I N -- Get arguments from command line */ ! 147: /* ! 148: Simple Unix-style command line parser, conforming with 'A Proposed Command ! 149: Syntax Standard for Unix Systems', Hemenway & Armitage, Unix/World, Vol.1, ! 150: No.3, 1984. ! 151: */ ! 152: cmdlin() { ! 153: char x; /* Local general-purpose int */ ! 154: cmarg = ""; /* Initialize globals */ ! 155: cmarg2 = ""; ! 156: action = cflg = 0; ! 157: ! 158: while (--xargc > 0) { /* Go through command line words */ ! 159: xargv++; ! 160: debug(F111,"xargv",*xargv,xargc); ! 161: if (**xargv == '-') { /* Got an option (begins with dash) */ ! 162: x = *(*xargv+1); /* Get the option letter */ ! 163: x = doarg(x); /* Go handle the option */ ! 164: if (x < 0) doexit(BAD_EXIT); ! 165: } else { /* No dash where expected */ ! 166: usage(); ! 167: doexit(BAD_EXIT); ! 168: } ! 169: } ! 170: debug(F101,"action","",action); ! 171: if (!local) { ! 172: if ((action == 'g') || (action == 'r') || ! 173: (action == 'c') || (cflg != 0)) ! 174: fatal("-l and -b required"); ! 175: } ! 176: if (*cmarg2 != 0) { ! 177: if ((action != 's') && (action != 'r') && ! 178: (action != 'v')) ! 179: fatal("-a without -s, -r, or -g"); ! 180: } ! 181: if ((action == 'v') && (stdouf) && (!local)) { ! 182: if (isatty(1)) ! 183: fatal("unredirected -k can only be used in local mode"); ! 184: } ! 185: if ((action == 's') || (action == 'v') || ! 186: (action == 'r') || (action == 'x')) { ! 187: if (local) displa = 1; ! 188: if (stdouf) { displa = 0; quiet = 1; } ! 189: } ! 190: ! 191: if (quiet) displa = 0; /* No display if quiet requested */ ! 192: ! 193: if (cflg) { ! 194: conect(); /* Connect if requested */ ! 195: if (action == 0) { ! 196: if (cnflg) conect(); /* And again if requested */ ! 197: doexit(GOOD_EXIT); /* Then exit indicating success */ ! 198: } ! 199: } ! 200: if (displa) concb(escape); /* (for console "interrupts") */ ! 201: return(action); /* Then do any requested protocol */ ! 202: } ! 203: ! 204: /* D O A R G -- Do a command-line argument. */ ! 205: ! 206: doarg(x) char x; { ! 207: int z; char *xp; ! 208: ! 209: xp = *xargv+1; /* Pointer for bundled args */ ! 210: while (x) { ! 211: switch (x) { ! 212: ! 213: case 'x': /* server */ ! 214: if (action) fatal("conflicting actions"); ! 215: action = 'x'; ! 216: break; ! 217: ! 218: case 'f': ! 219: if (action) fatal("conflicting actions"); ! 220: action = setgen('F',"","",""); ! 221: break; ! 222: ! 223: case 'r': /* receive */ ! 224: if (action) fatal("conflicting actions"); ! 225: action = 'v'; ! 226: break; ! 227: ! 228: case 'k': /* receive to stdout */ ! 229: if (action) fatal("conflicting actions"); ! 230: stdouf = 1; ! 231: action = 'v'; ! 232: break; ! 233: ! 234: case 's': /* send */ ! 235: if (action) fatal("conflicting actions"); ! 236: if (*(xp+1)) fatal("invalid argument bundling after -s"); ! 237: z = nfils = 0; /* Initialize file counter, flag */ ! 238: cmlist = xargv+1; /* Remember this pointer */ ! 239: while (--xargc > 0) { /* Traverse the list */ ! 240: *xargv++; ! 241: if (**xargv == '-') { /* Check for sending stdin */ ! 242: if (strcmp(*xargv,"-") != 0) break; ! 243: z++; ! 244: } ! 245: nfils++; /* Bump file counter */ ! 246: } ! 247: xargc++, *xargv--; /* Adjust argv/argc */ ! 248: if (nfils < 1) fatal("missing filename for -s"); ! 249: if (z > 1) fatal("-s: too many -'s"); ! 250: if (z == 1) { ! 251: if (nfils == 1) nfils = 0; ! 252: else fatal("invalid mixture of filenames and '-' in -s"); ! 253: } ! 254: if (nfils == 0) { ! 255: if (isatty(0)) fatal("sending from terminal not allowed"); ! 256: } ! 257: debug(F101,*xargv,"",nfils); ! 258: action = 's'; ! 259: break; ! 260: ! 261: /* cont'd... */ ! 262: ! 263: /* ...doarg(), cont'd */ ! 264: ! 265: case 'g': /* get */ ! 266: if (action) fatal("conflicting actions"); ! 267: if (*(xp+1)) fatal("invalid argument bundling after -g"); ! 268: *xargv++, xargc--; ! 269: if ((xargc == 0) || (**xargv == '-')) ! 270: fatal("missing filename for -g"); ! 271: cmarg = *xargv; ! 272: action = 'r'; ! 273: break; ! 274: ! 275: case 'c': /* connect before */ ! 276: cflg = 1; ! 277: break; ! 278: ! 279: case 'n': /* connect after */ ! 280: cnflg = 1; ! 281: break; ! 282: ! 283: case 'h': /* help */ ! 284: usage(); ! 285: return(-1); ! 286: ! 287: case 'a': /* "as" */ ! 288: if (*(xp+1)) fatal("invalid argument bundling after -a"); ! 289: *xargv++, xargc--; ! 290: if ((xargc < 1) || (**xargv == '-')) ! 291: fatal("missing name in -a"); ! 292: cmarg2 = *xargv; ! 293: break; ! 294: ! 295: case 'l': /* set line */ ! 296: if (*(xp+1)) fatal("invalid argument bundling after -l"); ! 297: *xargv++, xargc--; ! 298: if ((xargc < 1) || (**xargv == '-')) ! 299: fatal("communication line device name missing"); ! 300: strcpy(ttname,*xargv); ! 301: /* if (strcmp(ttname,dftty) == 0) local = dfloc; else local = 1; */ ! 302: local = (strcmp(ttname,CTTNAM) != 0); /* (better than old way) */ ! 303: debug(F101,"local","",local); ! 304: ttopen(ttname,&local,0); ! 305: break; ! 306: ! 307: case 'b': /* set baud */ ! 308: if (*(xp+1)) fatal("invalid argument bundling"); ! 309: *xargv++, xargc--; ! 310: if ((xargc < 1) || (**xargv == '-')) ! 311: fatal("missing baud"); ! 312: z = atoi(*xargv); /* Convert to number */ ! 313: if (chkspd(z) > -1) speed = z; /* Check it */ ! 314: else fatal("unsupported baud rate"); ! 315: break; ! 316: ! 317: case 'e': /* Extended packet length */ ! 318: if (*(xp+1)) fatal("invalid argument bundling"); ! 319: *xargv++, xargc--; ! 320: if ((xargc < 1) || (**xargv == '-')) ! 321: fatal("missing length"); ! 322: z = atoi(*xargv); /* Convert to number */ ! 323: if (z > 10 && z < maxrps) { ! 324: rpsiz = urpsiz = z; ! 325: if (z > 94) rpsiz = 94; /* Fallback if other Kermit can't */ ! 326: } else fatal("Unsupported packet length"); ! 327: break; ! 328: ! 329: case 'i': /* Treat files as binary */ ! 330: binary = 1; ! 331: break; ! 332: ! 333: /* cont'd... */ ! 334: ! 335: /* ...doarg(), cont'd */ ! 336: ! 337: ! 338: case 'w': /* File warning */ ! 339: warn = 1; ! 340: break; ! 341: ! 342: case 'q': /* Quiet */ ! 343: quiet = 1; ! 344: break; ! 345: ! 346: case 'd': /* debug */ ! 347: debopn("debug.log"); ! 348: break; ! 349: ! 350: case 'p': /* set parity */ ! 351: if (*(xp+1)) fatal("invalid argument bundling"); ! 352: *xargv++, xargc--; ! 353: if ((xargc < 1) || (**xargv == '-')) ! 354: fatal("missing parity"); ! 355: switch(x = **xargv) { ! 356: case 'e': ! 357: case 'o': ! 358: case 'm': ! 359: case 's': parity = x; break; ! 360: case 'n': parity = 0; break; ! 361: default: fatal("invalid parity"); ! 362: } ! 363: break; ! 364: ! 365: case 't': ! 366: turn = 1; /* Line turnaround handshake */ ! 367: turnch = XON; /* XON is turnaround character */ ! 368: duplex = 1; /* Half duplex */ ! 369: flow = 0; /* No flow control */ ! 370: break; ! 371: ! 372: default: ! 373: fatal("invalid argument, type 'kermit -h' for help"); ! 374: } ! 375: ! 376: x = *++xp; /* See if options are bundled */ ! 377: } ! 378: return(0); ! 379: } ! 380: ! 381: /* Misc */ ! 382: ! 383: fatal(msg) char *msg; { /* Fatal error message */ ! 384: fprintf(stderr,"\r\nFatal: %s\n",msg); ! 385: tlog(F110,"Fatal:",msg,0l); ! 386: doexit(BAD_EXIT); /* Exit indicating failure */ ! 387: } ! 388: ! 389: ! 390: ermsg(msg) char *msg; { /* Print error message */ ! 391: if (!quiet) fprintf(stderr,"\r\n%s - %s\n",cmerrp,msg); ! 392: tlog(F110,"Error -",msg,0l); ! 393: } ! 394: ! 395: /* Interactive command parser */ ! 396: ! 397: ! 398: /* Top-Level Keyword Table */ ! 399: ! 400: struct keytab cmdtab[] = { ! 401: "!", XXSHE, 0, ! 402: "%", XXCOM, CM_INV, ! 403: "bye", XXBYE, 0, ! 404: "c", XXCON, CM_INV, ! 405: "close", XXCLO, 0, ! 406: "connect", XXCON, 0, ! 407: "cwd", XXCWD, 0, ! 408: "dial", XXDIAL, 0, ! 409: "directory", XXDIR, 0, ! 410: "echo", XXECH, 0, ! 411: "exit", XXEXI, 0, ! 412: "finish", XXFIN, 0, ! 413: "get", XXGET, 0, ! 414: "help", XXHLP, 0, ! 415: "log", XXLOG, 0, ! 416: "quit", XXQUI, 0, ! 417: "r", XXREC, CM_INV, ! 418: "receive", XXREC, 0, ! 419: "remote", XXREM, 0, ! 420: "s", XXSEN, CM_INV, ! 421: "script", XXLOGI, 0, ! 422: "send", XXSEN, 0, ! 423: "server", XXSER, 0, ! 424: "set", XXSET, 0, ! 425: "show", XXSHO, 0, ! 426: "space", XXSPA, 0, ! 427: "statistics", XXSTA, 0, ! 428: "take", XXTAK, 0 ! 429: }; ! 430: int ncmd = (sizeof(cmdtab) / sizeof(struct keytab)); ! 431: ! 432: /* Parameter keyword table */ ! 433: ! 434: struct keytab prmtab[] = { ! 435: "baud", XYSPEE, CM_INV, ! 436: "block-check", XYCHKT, 0, ! 437: "delay", XYDELA, 0, ! 438: "duplex", XYDUPL, 0, ! 439: "end-of-packet", XYEOL, CM_INV, /* moved to send/receive */ ! 440: "escape-character", XYESC, 0, ! 441: "file", XYFILE, 0, ! 442: "flow-control", XYFLOW, 0, ! 443: "handshake", XYHAND, 0, ! 444: "incomplete", XYIFD, 0, ! 445: "line", XYLINE, 0, ! 446: "modem-dialer", XYMODM, 0, ! 447: "packet-length", XYLEN, CM_INV, /* moved to send/receive */ ! 448: "pad-character", XYPADC, CM_INV, /* moved to send/receive */ ! 449: "padding", XYNPAD, CM_INV, /* moved to send/receive */ ! 450: "parity", XYPARI, 0, ! 451: "prompt", XYPROM, 0, ! 452: "receive", XYRECV, 0, ! 453: "retry", XYRETR, 0, ! 454: "send", XYSEND, 0, ! 455: "speed", XYSPEE, 0, ! 456: "start-of-packet", XYMARK, CM_INV, /* moved to send/receive */ ! 457: "terminal", XYTERM, 0, ! 458: "timeout", XYTIMO, CM_INV /* moved to send/receive */ ! 459: }; ! 460: int nprm = (sizeof(prmtab) / sizeof(struct keytab)); /* How many parameters */ ! 461: ! 462: ! 463: /* Remote Command Table */ ! 464: ! 465: struct keytab remcmd[] = { ! 466: "cwd", XZCWD, 0, ! 467: "delete", XZDEL, 0, ! 468: "directory", XZDIR, 0, ! 469: "help", XZHLP, 0, ! 470: "host", XZHOS, 0, ! 471: "space", XZSPA, 0, ! 472: "type", XZTYP, 0, ! 473: "who", XZWHO, 0 ! 474: }; ! 475: int nrmt = (sizeof(remcmd) / sizeof(struct keytab)); ! 476: ! 477: struct keytab logtab[] = { ! 478: "debugging", LOGD, 0, ! 479: "packets", LOGP, 0, ! 480: "session", LOGS, 0, ! 481: "transactions", LOGT, 0 ! 482: }; ! 483: int nlog = (sizeof(logtab) / sizeof(struct keytab)); ! 484: ! 485: /* Show command arguments */ ! 486: ! 487: #define SHPAR 0 /* Parameters */ ! 488: #define SHVER 1 /* Versions */ ! 489: ! 490: struct keytab shotab[] = { ! 491: "parameters", SHPAR, 0, ! 492: "versions", SHVER, 0 ! 493: }; ! 494: ! 495: /* C M D I N I -- Initialize the interactive command parser */ ! 496: ! 497: cmdini() { ! 498: ! 499: #ifdef AMIGA ! 500: congm(); ! 501: concb(escape); ! 502: #endif ! 503: tlevel = -1; /* Take file level */ ! 504: cmsetp("C-Kermit>"); /* Set default prompt */ ! 505: ! 506: /* Look for init file in home or current directory. */ ! 507: ! 508: homdir = zhome(); ! 509: lp = line; ! 510: lp[0] = '\0'; ! 511: if (homdir) { ! 512: strcpy(lp,homdir); ! 513: if (lp[0] == '/') strcat(lp,"/"); ! 514: } ! 515: strcat(lp,KERMRC); ! 516: #ifdef AMIGA ! 517: reqoff(); /* disable requestors */ ! 518: #endif ! 519: if ((tfile[0] = fopen(line,"r")) != NULL) { ! 520: tlevel = 0; ! 521: debug(F110,"init file",line,0); ! 522: } ! 523: if (homdir && (tlevel < 0)) { ! 524: strcpy(lp,KERMRC); ! 525: if ((tfile[0] = fopen(line,"r")) != NULL) { ! 526: tlevel = 0; ! 527: debug(F110,"init file",line,0); ! 528: } else { ! 529: debug(F100,"no init file","",0); ! 530: } ! 531: } ! 532: #ifdef AMIGA ! 533: reqpop(); /* restore requestors */ ! 534: #else ! 535: congm(); /* Get console tty modes */ ! 536: #endif ! 537: } ! 538: ! 539: /* Display version herald and initial prompt */ ! 540: ! 541: herald() { ! 542: if (!backgrd) printf("%s,%s\nType ? for help\n",versio,ckxsys); ! 543: } ! 544: ! 545: ! 546: /* T R A P -- Terminal interrupt handler */ ! 547: ! 548: trap() { ! 549: debug(F100,"terminal interrupt...","",0); ! 550: doexit(GOOD_EXIT); /* Exit indicating success */ ! 551: } ! 552: ! 553: /* S T P T R A P -- Handle SIGTSTP signals */ ! 554: ! 555: stptrap() { ! 556: conres(); /* Reset the console */ ! 557: #ifdef SIGTSTP ! 558: kill(0, SIGSTOP); /* If job control, suspend the job */ ! 559: #else ! 560: doexit(GOOD_EXIT); /* Probably won't happen otherwise */ ! 561: #endif ! 562: concb(); /* Put console back in Kermit mode */ ! 563: } ! 564: ! 565: /* P A R S E R -- Top-level interactive command parser. */ ! 566: ! 567: parser() { ! 568: int xx, cbn; ! 569: char *cbp; ! 570: ! 571: #ifdef AMIGA ! 572: reqres(); /* restore AmigaDOS requestors */ ! 573: #endif ! 574: concb(escape); /* Put console in cbreak mode. */ ! 575: conint(trap); /* Turn on console terminal interrupts. */ ! 576: /* ! 577: sstate becomes nonzero when a command has been parsed that requires some ! 578: action from the protocol module. Any non-protocol actions, such as local ! 579: directory listing or terminal emulation, are invoked directly from below. ! 580: */ ! 581: if (local && !backgrd) printf("\n"); /*** Temporary kludge ***/ ! 582: sstate = 0; /* Start with no start state. */ ! 583: while (sstate == 0) { /* Parse cmds until action requested */ ! 584: while ((tlevel > -1) && feof(tfile[tlevel])) { /* If end of take */ ! 585: fclose(tfile[tlevel--]); /* file, close it. */ ! 586: cmini(ckxech); /* and clear the cmd buffer. */ ! 587: if (tlevel < 0) { /* Just popped out of cmd files? */ ! 588: conint(trap); /* Check background stuff again. */ ! 589: return(0); /* End of init file or whatever. */ ! 590: } ! 591: } ! 592: debug(F101,"tlevel","",tlevel); ! 593: if (tlevel > -1) { /* If in take file */ ! 594: cbp = cmdbuf; /* Get the next line. */ ! 595: cbn = CMDBL; ! 596: ! 597: /* Loop to get next command line and all continuation lines from take file. */ ! 598: ! 599: again: if (fgets(line,cbn,tfile[tlevel]) == NULL) continue; ! 600: lp = line; /* Got one, copy it. */ ! 601: while (*cbp++ = *lp++) ! 602: if (--cbn < 1) fatal("Command too long for internal buffer"); ! 603: if (*(cbp - 3) == '\\') { /* Continued on next line? */ ! 604: cbp -= 3; /* If so, back up pointer, */ ! 605: goto again; /* go back, get next line. */ ! 606: } ! 607: stripq(cmdbuf); /* Strip any quotes from cmd buffer. */ ! 608: ! 609: } else { /* No take file, get typein. */ ! 610: ! 611: if (!backgrd) prompt(); /* Issue interactive prompt. */ ! 612: cmini(ckxech); ! 613: } ! 614: repars = 1; ! 615: displa = 0; ! 616: while (repars) { ! 617: cmres(); /* Reset buffer pointers. */ ! 618: xx = cmkey(cmdtab,ncmd,"Command",""); ! 619: debug(F101,"top-level cmkey","",xx); ! 620: switch (docmd(xx)) { ! 621: case -4: /* EOF */ ! 622: doexit(GOOD_EXIT); /* ...exit successfully */ ! 623: case -1: /* Reparse needed */ ! 624: repars = 1; ! 625: continue; ! 626: case -2: /* Invalid command given */ ! 627: if (backgrd) /* if in background, terminate */ ! 628: fatal("Kermit command error in background execution"); ! 629: if (tlevel > -1) { /* If in take file, quit */ ! 630: ermsg("Kermit command error: take file terminated."); ! 631: fclose(tfile[tlevel]); ! 632: tlevel--; ! 633: } ! 634: cmini(ckxech); /* (fall thru) */ ! 635: case -3: /* Empty command OK at top level */ ! 636: default: /* Anything else (fall thru) */ ! 637: repars = 0; /* No reparse, get new command. */ ! 638: continue; ! 639: } ! 640: } ! 641: } ! 642: /* Got an action command; disable terminal interrupts and return start state */ ! 643: ! 644: if (!local) connoi(); /* Interrupts off only if remote */ ! 645: return(sstate); ! 646: } ! 647: ! 648: /* D O E X I T -- Exit from the program. */ ! 649: ! 650: doexit(exitstat) int exitstat; { ! 651: ! 652: ttclos(); /* Close external line, if any */ ! 653: if (local) { ! 654: strcpy(ttname,dftty); /* Restore default tty */ ! 655: local = dfloc; /* And default remote/local status */ ! 656: } ! 657: if (!quiet) conres(); /* Restore console terminal. */ ! 658: if (!quiet) connoi(); /* Turn off console interrupt traps. */ ! 659: ! 660: if (deblog) { /* Close any open logs. */ ! 661: debug(F100,"Debug Log Closed","",0); ! 662: *debfil = '\0'; ! 663: deblog = 0; ! 664: zclose(ZDFILE); ! 665: } ! 666: if (pktlog) { ! 667: *pktfil = '\0'; ! 668: pktlog = 0; ! 669: zclose(ZPFILE); ! 670: } ! 671: if (seslog) { ! 672: *sesfil = '\0'; ! 673: seslog = 0; ! 674: zclose(ZSFILE); ! 675: } ! 676: if (tralog) { ! 677: tlog(F100,"Transaction Log Closed","",0l); ! 678: *trafil = '\0'; ! 679: tralog = 0; ! 680: zclose(ZTFILE); ! 681: } ! 682: syscleanup(); ! 683: exit(exitstat); /* Exit from the program. */ ! 684: } ! 685: ! 686: /* B L D L E N -- Make length-encoded copy of string */ ! 687: ! 688: char * ! 689: bldlen(str,dest) char *str, *dest; { ! 690: int len; ! 691: len = strlen(str); ! 692: *dest = tochar(len); ! 693: strcpy(dest+1,str); ! 694: return(dest+len+1); ! 695: } ! 696: ! 697: ! 698: /* S E T G E N -- Construct a generic command */ ! 699: ! 700: setgen(type,arg1,arg2,arg3) char type, *arg1, *arg2, *arg3; { ! 701: char *upstr, *cp; ! 702: ! 703: cp = cmdstr; ! 704: *cp++ = type; ! 705: *cp = NUL; ! 706: if (*arg1 != NUL) { ! 707: upstr = bldlen(arg1,cp); ! 708: if (*arg2 != NUL) { ! 709: upstr = bldlen(arg2,upstr); ! 710: if (*arg3 != NUL) bldlen(arg3,upstr); ! 711: } ! 712: } ! 713: cmarg = cmdstr; ! 714: debug(F110,"setgen",cmarg,0); ! 715: ! 716: return('g'); ! 717: } ! 718: ! 719: /* D O C M D -- Do a command */ ! 720: ! 721: /* ! 722: Returns: ! 723: -2: user typed an illegal command ! 724: -1: reparse needed ! 725: 0: parse was successful (even tho command may have failed). ! 726: */ ! 727: ! 728: docmd(cx) int cx; { ! 729: int x, y; ! 730: char *s; ! 731: ! 732: switch (cx) { ! 733: ! 734: case -4: /* EOF */ ! 735: if (!quiet && !backgrd) printf("\r\n"); ! 736: doexit(GOOD_EXIT); ! 737: case -3: /* Null command */ ! 738: return(0); ! 739: case -2: /* Error */ ! 740: case -1: /* Reparse needed */ ! 741: return(cx); ! 742: ! 743: case XXBYE: /* bye */ ! 744: if ((x = cmcfm()) < 0) return(x); ! 745: if (!local) { ! 746: printf("You have to 'set line' first\n"); ! 747: return(0); ! 748: } ! 749: sstate = setgen('L',"","",""); ! 750: return(0); ! 751: ! 752: case XXCOM: /* comment */ ! 753: if ((x = cmtxt("Text of comment line","",&s)) < 0) return(x); ! 754: return(0); ! 755: ! 756: case XXCON: /* connect */ ! 757: if ((x = cmcfm()) < 0) return(x); ! 758: return(doconect()); ! 759: ! 760: case XXCWD: ! 761: #ifdef AMIGA ! 762: if (cmtxt("Name of local directory, or carriage return","",&s) < 0) ! 763: return(-1); ! 764: /* if no name, just print directory name */ ! 765: if (*s) { ! 766: if (chdir(s)) perror(s); ! 767: cwdf = 1; ! 768: } ! 769: if (getcwd(line, sizeof(line)) == NULL) ! 770: printf("Current directory name not available.\n"); ! 771: else ! 772: if (!backgrd) printf("%s\n", line); ! 773: #else ! 774: if (cmtxt("Name of local directory, or carriage return",homdir,&s) < 0) ! 775: return(-1); ! 776: if (chdir(s)) perror(s); ! 777: cwdf = 1; ! 778: system(PWDCMD); ! 779: #endif ! 780: return(0); ! 781: ! 782: case XXCLO: ! 783: x = cmkey(logtab,nlog,"Which log to close",""); ! 784: if (x == -3) { ! 785: printf("?You must tell which log\n"); ! 786: return(-2); ! 787: } ! 788: if (x < 0) return(x); ! 789: if ((y = cmcfm()) < 0) return(y); ! 790: switch (x) { ! 791: ! 792: case LOGD: ! 793: if (deblog == 0) { ! 794: printf("?Debugging log wasn't open\n"); ! 795: return(0); ! 796: } ! 797: *debfil = '\0'; ! 798: deblog = 0; ! 799: return(zclose(ZDFILE)); ! 800: ! 801: case LOGP: ! 802: if (pktlog == 0) { ! 803: printf("?Packet log wasn't open\n"); ! 804: return(0); ! 805: } ! 806: *pktfil = '\0'; ! 807: pktlog = 0; ! 808: return(zclose(ZPFILE)); ! 809: ! 810: case LOGS: ! 811: if (seslog == 0) { ! 812: printf("?Session log wasn't open\n"); ! 813: return(0); ! 814: } ! 815: *sesfil = '\0'; ! 816: seslog = 0; ! 817: return(zclose(ZSFILE)); ! 818: ! 819: case LOGT: ! 820: if (tralog == 0) { ! 821: printf("?Transaction log wasn't open\n"); ! 822: return(0); ! 823: } ! 824: *trafil = '\0'; ! 825: tralog = 0; ! 826: return(zclose(ZTFILE)); ! 827: ! 828: default: ! 829: printf("\n?Unexpected log designator - %ld\n", x); ! 830: return(0); ! 831: } ! 832: ! 833: case XXDIAL: /* dial number */ ! 834: if ((x = cmtxt("Number to be dialed","",&s)) < 0) return(x); ! 835: return(ckdial(s)); ! 836: ! 837: case XXDIR: /* directory */ ! 838: #ifdef AMIGA ! 839: if ((x = cmtxt("Directory/file specification","",&s)) < 0) return(x); ! 840: #else ! 841: #ifdef datageneral ! 842: if ((x = cmtxt("Directory/file specification","+",&s)) < 0) return(x); ! 843: #else ! 844: if ((x = cmtxt("Directory/file specification",".",&s)) < 0) return(x); ! 845: #endif ! 846: #endif ! 847: lp = line; ! 848: sprintf(lp,"%s %s",DIRCMD,s); ! 849: system(line); ! 850: return(0); ! 851: ! 852: ! 853: case XXECH: /* echo */ ! 854: if ((x = cmtxt("Material to be echoed","",&s)) < 0) return(x); ! 855: for ( ; *s; s++) { ! 856: if ((x = *s) == 0134) { /* Convert octal escapes */ ! 857: s++; /* up to 3 digits */ ! 858: for (x = y = 0; *s >= '0' && *s <= '7' && y < 3; s++,y++) { ! 859: x = x * 8 + (int) *s - 48; ! 860: } ! 861: s--; ! 862: } ! 863: putchar(x); ! 864: } ! 865: printf("\n"); ! 866: return(0); ! 867: ! 868: case XXQUI: /* quit, exit */ ! 869: case XXEXI: ! 870: if ((x = cmcfm()) > -1) doexit(GOOD_EXIT); ! 871: else return(x); ! 872: ! 873: case XXFIN: /* finish */ ! 874: if ((x = cmcfm()) < 0) return(x); ! 875: if (!local) { ! 876: printf("You have to 'set line' first\n"); ! 877: return(0); ! 878: } ! 879: sstate = setgen('F',"","",""); ! 880: return(0); ! 881: ! 882: case XXGET: /* get */ ! 883: if (!local) { ! 884: printf("\nYou have to 'set line' first\n"); ! 885: return(0); ! 886: } ! 887: x = cmtxt("Name of remote file(s), or carriage return","",&cmarg); ! 888: if ((x == -2) || (x == -1)) return(x); ! 889: ! 890: /* If foreign file name omitted, get foreign and local names separately */ ! 891: ! 892: x = 0; /* For some reason cmtxt returns 1 */ ! 893: if (*cmarg == NUL) { ! 894: ! 895: if (tlevel > -1) { /* Input is from take file */ ! 896: ! 897: if (fgets(line,100,tfile[tlevel]) == NULL) ! 898: fatal("take file ends prematurely in 'get'"); ! 899: debug(F110,"take-get 2nd line",line,0); ! 900: stripq(line); ! 901: for (x = strlen(line); ! 902: x > 0 && (line[x-1] == '\n' || line[x-1] == '\r'); ! 903: x--) ! 904: line[x-1] = '\0'; ! 905: cmarg = line; ! 906: if (fgets(cmdbuf,CMDBL,tfile[tlevel]) == NULL) ! 907: fatal("take file ends prematurely in 'get'"); ! 908: stripq(cmdbuf); ! 909: for (x = strlen(cmdbuf); ! 910: x > 0 && (cmdbuf[x-1] == '\n' || cmdbuf[x-1] == '\r'); ! 911: x--) ! 912: cmdbuf[x-1] = '\0'; ! 913: if (*cmdbuf == NUL) cmarg2 = line; else cmarg2 = cmdbuf; ! 914: x = 0; /* Return code */ ! 915: ! 916: } else { /* Input is from terminal */ ! 917: ! 918: char psave[40]; /* Save old prompt */ ! 919: cmsavp(psave,40); ! 920: cmsetp(" Remote file specification: "); /* Make new one */ ! 921: cmini(ckxech); ! 922: x = -1; ! 923: if (!backgrd) prompt(); ! 924: while (x == -1) { /* Prompt till they answer */ ! 925: x = cmtxt("Name of remote file(s)","",&cmarg); ! 926: debug(F111," cmtxt",cmarg,x); ! 927: } ! 928: if (x < 0) { ! 929: cmsetp(psave); ! 930: return(x); ! 931: } ! 932: if (*cmarg == NUL) { /* If user types a bare CR, */ ! 933: printf("(cancelled)\n"); /* Forget about this. */ ! 934: cmsetp(psave); /* Restore old prompt, */ ! 935: return(0); /* and return. */ ! 936: } ! 937: strcpy(line,cmarg); /* Make a safe copy */ ! 938: cmarg = line; ! 939: cmsetp(" Local name to store it under: "); /* New prompt */ ! 940: cmini(ckxech); ! 941: x = -1; ! 942: if (!backgrd) prompt(); ! 943: while (x == -1) { /* Again, parse till answered */ ! 944: x = cmofi("Local file name","",&cmarg2); ! 945: } ! 946: if (x == -3) { /* If bare CR, */ ! 947: printf("(cancelled)\n"); /* escape from this... */ ! 948: cmsetp(psave); /* Restore old prompt, */ ! 949: return(0); /* and return. */ ! 950: } else if (x < 0) return(x); /* Handle parse errors. */ ! 951: ! 952: x = -1; /* Get confirmation. */ ! 953: while (x == -1) x = cmcfm(); ! 954: cmsetp(psave); /* Restore old prompt. */ ! 955: } ! 956: } ! 957: if (x == 0) { /* Good return from cmtxt or cmcfm, */ ! 958: sstate = 'r'; /* set start state. */ ! 959: if (local) displa = 1; ! 960: } ! 961: return(x); ! 962: ! 963: case XXHLP: /* Help */ ! 964: x = cmkey(cmdtab,ncmd,"C-Kermit command","help"); ! 965: return(dohlp(x)); ! 966: ! 967: case XXLOG: /* Log */ ! 968: x = cmkey(logtab,nlog,"What to log",""); ! 969: if (x == -3) { ! 970: printf("?You must specify what is to be logged\n"); ! 971: return(-2); ! 972: } ! 973: if (x < 0) return(x); ! 974: return(dolog(x)); ! 975: ! 976: case XXLOGI: /* Send script remote system */ ! 977: if ((x = cmtxt("Text of login script","",&s)) < 0) return(x); ! 978: return( login(s) ); /* Return 0=completed, -2=failed */ ! 979: ! 980: case XXREC: /* Receive */ ! 981: cmarg2 = ""; ! 982: x = cmofi("Name under which to store the file, or CR","",&cmarg2); ! 983: if ((x == -1) || (x == -2)) return(x); ! 984: debug(F111,"cmofi cmarg2",cmarg2,x); ! 985: if ((x = cmcfm()) < 0) return(x); ! 986: sstate = 'v'; ! 987: if (local) displa = 1; ! 988: return(0); ! 989: ! 990: case XXREM: /* Remote */ ! 991: if (!local) { ! 992: printf("\nYou have to 'set line' first\n"); ! 993: return(-2); ! 994: } ! 995: x = cmkey(remcmd,nrmt,"Remote Kermit server command",""); ! 996: if (x == -3) { ! 997: printf("?You must specify a command for the remote server\n"); ! 998: return(-2); ! 999: } ! 1000: return(dormt(x)); ! 1001: ! 1002: case XXSEN: /* Send */ ! 1003: cmarg = cmarg2 = ""; ! 1004: if ((x = cmifi("File(s) to send","",&s,&y)) < 0) { ! 1005: if (x == -3) { ! 1006: printf("?A file specification is required\n"); ! 1007: return(-2); ! 1008: } ! 1009: return(x); ! 1010: } ! 1011: nfils = -1; /* Files come from internal list. */ ! 1012: strcpy(line,s); /* Save copy of string just parsed. */ ! 1013: debug(F101,"Send: wild","",y); ! 1014: *cmarg2 = '\0'; /* Initialize send-as name */ ! 1015: if (y == 0) { ! 1016: if ((x = cmtxt("Name to send it with","",&cmarg2)) < 0) return(x); ! 1017: } else { ! 1018: if ((x = cmcfm()) < 0) return(x); ! 1019: } ! 1020: cmarg = line; /* File to send */ ! 1021: debug(F110,"Sending:",cmarg,0); ! 1022: if (*cmarg2 != '\0') debug(F110," as:",cmarg2,0); ! 1023: sstate = 's'; /* Set start state */ ! 1024: if (local) displa = 1; ! 1025: return(0); ! 1026: ! 1027: case XXSER: /* Server */ ! 1028: if ((x = cmcfm()) < 0) return(x); ! 1029: sstate = 'x'; ! 1030: if (local) displa = 1; ! 1031: #ifdef AMIGA ! 1032: reqoff(); /* no DOS requestors while server */ ! 1033: #endif ! 1034: return(0); ! 1035: ! 1036: case XXSET: /* Set */ ! 1037: x = cmkey(prmtab,nprm,"Parameter",""); ! 1038: if (x == -3) { ! 1039: printf("?You must specify a parameter to set\n"); ! 1040: return(-2); ! 1041: } ! 1042: if (x < 0) return(x); ! 1043: return(doprm(x)); ! 1044: ! 1045: /* XXSHE code by H. Fischer; copyright rights assigned to Columbia Univ */ ! 1046: /* ! 1047: Adapted to use getpwuid to find login shell because many systems do not ! 1048: have SHELL in environment, and to use direct calling of shell rather ! 1049: than intermediate system() call. -- H. Fischer ! 1050: */ ! 1051: case XXSHE: /* Local shell command */ ! 1052: { ! 1053: int pid; ! 1054: #ifdef AMIGA ! 1055: if (cmtxt("Command to execute","",&s) < 0) return(-1); ! 1056: #else ! 1057: if (cmtxt("Unix shell command to execute","",&s) < 0) return(-1); ! 1058: #endif ! 1059: conres(); /* Make console normal */ ! 1060: #ifdef AMIGA ! 1061: system(s); ! 1062: #else ! 1063: #ifdef MSDOS ! 1064: zxcmd(s); ! 1065: #else ! 1066: #ifdef vax11c ! 1067: ! 1068: system(s); /* Best we can do for VMS? */ ! 1069: #else /* All Unix systems... */ ! 1070: #ifdef datageneral ! 1071: if (*s == NUL) /* Interactive shell requested? */ ! 1072: #ifdef mvux ! 1073: system("/bin/sh "); ! 1074: #else ! 1075: system("x :cli prefix Kermit_Baby:"); ! 1076: #endif ! 1077: else /* Otherwise, */ ! 1078: system(s); /* Best for aos/vs?? */ ! 1079: ! 1080: #else /* All Unix systems... */ ! 1081: #ifdef apollo ! 1082: if ((pid = vfork()) == 0) { /* Make child quickly */ ! 1083: char *shpath, *shname, *shptr; /* For finding desired shell */ ! 1084: ! 1085: if ((shpath = getenv("SHELL")) == NULL) shpath = "/com/sh"; ! 1086: #else ! 1087: ! 1088: if ((pid = fork()) == 0) { /* Make child */ ! 1089: char *shpath, *shname, *shptr; /* For finding desired shell */ ! 1090: struct passwd *p; ! 1091: extern struct passwd * getpwuid(); ! 1092: extern int getuid(); ! 1093: char *defShel = "/bin/sh"; /* Default */ ! 1094: ! 1095: p = getpwuid( getuid() ); /* Get login data */ ! 1096: if ( p == (struct passwd *) NULL || !*(p->pw_shell) ) ! 1097: shpath = defShel; ! 1098: else ! 1099: shpath = p->pw_shell; ! 1100: #endif ! 1101: shptr = shname = shpath; ! 1102: while (*shptr != '\0') ! 1103: if (*shptr++ == '/') shname = shptr; ! 1104: ! 1105: /* Remove following uid calls if they cause trouble */ ! 1106: #ifdef BSD4 ! 1107: setegid(getgid()); /* Override 4.3BSD csh security */ ! 1108: seteuid(getuid()); /* checks. */ ! 1109: #endif ! 1110: ! 1111: if (*s == NUL) /* Interactive shell requested? */ ! 1112: execl(shpath,shname,"-i",NULL); /* Yes, do that */ ! 1113: else /* Otherwise, */ ! 1114: execl(shpath,shname,"-c",s,NULL); /* exec the given command */ ! 1115: exit(BAD_EXIT); } /* Just punt if it didn't work */ ! 1116: ! 1117: else { /* Parent */ ! 1118: ! 1119: int wstat; /* Kermit must wait for child */ ! 1120: SIGTYP (*istat)(), (*qstat)(); ! 1121: ! 1122: istat = signal(SIGINT,SIG_IGN); /* Let the fork handle keyboard */ ! 1123: qstat = signal(SIGQUIT,SIG_IGN); /* interrupts itself... */ ! 1124: ! 1125: while (((wstat = wait((int *)0)) != pid) && (wstat != -1)) ! 1126: /* Wait for fork */ ! 1127: signal(SIGINT,istat); /* Restore interrupts */ ! 1128: signal(SIGQUIT,qstat); ! 1129: } ! 1130: #endif ! 1131: #endif ! 1132: #endif ! 1133: #endif ! 1134: concb(escape); /* Console back in cbreak mode */ ! 1135: return(0); ! 1136: } ! 1137: ! 1138: case XXSHO: /* Show */ ! 1139: x = cmkey(shotab,2,"","parameters"); ! 1140: if (x < 0) return(x); ! 1141: if ((y = cmcfm()) < 0) return(y); ! 1142: switch (x) { ! 1143: ! 1144: case SHPAR: ! 1145: shopar(); ! 1146: break; ! 1147: ! 1148: case SHVER: ! 1149: printf("\nVersions:\n %s\n %s\n",versio,protv); ! 1150: printf(" %s\n",fnsv); ! 1151: printf(" %s\n %s\n",cmdv,userv); ! 1152: printf(" %s for%s\n",ckxv,ckxsys); ! 1153: printf(" %s for%s\n",ckzv,ckzsys); ! 1154: printf(" %s\n",connv); ! 1155: printf(" %s\n %s\n\n",dialv,loginv); ! 1156: break; ! 1157: ! 1158: default: ! 1159: printf("\nNothing to show...\n"); ! 1160: break; ! 1161: } ! 1162: return(0); ! 1163: ! 1164: case XXSPA: /* space */ ! 1165: #ifdef datageneral ! 1166: /* The DG can take an argument after its "space" command. */ ! 1167: if ((x = cmtxt("Confirm, or local directory name","",&s)) < 0) return(x); ! 1168: if (*s == NULL) system(SPACMD); ! 1169: else { ! 1170: char *cp; ! 1171: cp = alloc(strlen(s) + 7); /* For "space *s" */ ! 1172: strcpy(cp,"space "), strcat(cp,s); ! 1173: system(cp); ! 1174: free(cp); ! 1175: } ! 1176: #else ! 1177: if ((x = cmcfm()) < 0) return(x); ! 1178: system(SPACMD); ! 1179: #endif ! 1180: return(0); ! 1181: ! 1182: case XXSTA: /* statistics */ ! 1183: if ((x = cmcfm()) < 0) return(x); ! 1184: return(dostat()); ! 1185: ! 1186: case XXTAK: /* take */ ! 1187: if (tlevel > MAXTAKE-1) { ! 1188: printf("?Take files nested too deeply\n"); ! 1189: return(-2); ! 1190: } ! 1191: if ((y = cmifi("C-Kermit command file","",&s,&x)) < 0) { ! 1192: if (y == -3) { ! 1193: printf("?A file specification is required\n"); ! 1194: return(-2); ! 1195: } else return(y); ! 1196: } ! 1197: if (x != 0) { ! 1198: printf("?Wildcards not allowed in command file name\n"); ! 1199: return(-2); ! 1200: } ! 1201: strcpy(line,s); /* Make a safe copy of the string */ ! 1202: if ((y = cmcfm()) < 0) return(y); ! 1203: if ((tfile[++tlevel] = fopen(line,"r")) == NULL) { ! 1204: perror(line); ! 1205: debug(F110,"Failure to open",line,0); ! 1206: tlevel--; ! 1207: } ! 1208: return(0); ! 1209: ! 1210: default: ! 1211: printf("Not available - %s\n",cmdbuf); ! 1212: return(-2); ! 1213: } ! 1214: } ! 1215: ! 1216: /* D O C O N E C T -- Do the connect command */ ! 1217: ! 1218: /* Note, we don't call this directly from dial, because we need to give */ ! 1219: /* the user a chance to change parameters (e.g. parity) after the */ ! 1220: /* connection is made. */ ! 1221: ! 1222: doconect() { ! 1223: int x; ! 1224: conres(); /* Put console back to normal */ ! 1225: x = conect(); /* Connect */ ! 1226: concb(escape); /* Put console into cbreak mode, */ ! 1227: return(x); /* for more command parsing. */ ! 1228: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.