├── .gitignore ├── .gitmodules ├── Dockerfile ├── FTPD.MVP ├── FTPD.XMI ├── FTPD.conf ├── README.MD ├── install.jcl ├── original ├── FTPD_PROC.jcl ├── MAKEFTPD.jcl ├── MAKEXCTL.jcl ├── Original_readme.txt ├── README.md └── ftpd_rac.xmi └── source ├── README.md ├── build ├── BUILD.md ├── autobuild.py ├── generate_ftpdrakf.py ├── generate_install.py ├── link_ftpd.template └── objscan_input.nam ├── c ├── ftpd.c └── mvsdirs.h └── hlasm ├── FTPAUTH.hlasm ├── FTPDXCTL.hlasm ├── FTPLGOUT.hlasm ├── FTPLOGIN.hlasm └── FTPSU.hlasm /.gitignore: -------------------------------------------------------------------------------- 1 | **/reader.jcl 2 | **/ftpd.obj 3 | **/ftpdrakf.punch 4 | **/ftpdrac.obj 5 | **/ftpd_rac.pch 6 | **/ftpdrac.pch 7 | **/ftpd.load 8 | **/output.load 9 | **/ftpdrac.pch 10 | **/list.out 11 | 01_update_rakf.jcl 12 | 02_install_ftpd.jcl 13 | **/jcc/ 14 | **/01_assemble_ftp_objects.jcl 15 | **/02_ftprakf_asm.jcl 16 | **/.vscode -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "source/build/automvs"] 2 | path = source/build/automvs 3 | url = https://github.com/MVS-sysgen/automvs.git 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mainframed767/mvsce:2.0.1 as ftpd_objects 2 | # Install rdrprep 3 | RUN unset LD_LIBRARY_PATH && apt-get update && apt-get install -yq git build-essential python3-pip 4 | WORKDIR /build 5 | ADD source/build /build/ 6 | ADD source/hlasm/ /build/hlasm/ 7 | RUN pip3 install ebcdic 8 | RUN python3 generate_ftpdrakf.py ./hlasm 9 | RUN python3 -u autobuild.py --objects -d -m /MVSCE 10 | 11 | FROM mainframed767/jcc:wine as compiler 12 | # With the objects we can compile ftpd.c 13 | WORKDIR / 14 | COPY source/c/ /c/ 15 | COPY --from=ftpd_objects /build/ftpdrakf.punch /c/ 16 | COPY source/build/objscan_input.nam /c 17 | WORKDIR /c 18 | RUN wine /jcc/jcc.exe -I/jcc/include -I/c -D__MVS_ -o -list=list.out /c/ftpd.c 19 | RUN /jcc/objscan ftpdrakf.punch objscan_input.nam ftpdrac.obj 20 | RUN /jcc/prelink -r /jcc/objs ftpd.load ftpd.obj ftpdrac.obj 21 | 22 | FROM mainframed767/mvsce:2.0.1 23 | RUN unset LD_LIBRARY_PATH && apt-get update && apt-get install -yq git build-essential python3-pip 24 | WORKDIR / 25 | RUN git clone --depth 1 https://github.com/mvslovers/rdrprep.git 26 | WORKDIR /rdrprep 27 | RUN make && make install 28 | RUN pip3 install ebcdic requests 29 | WORKDIR /XMI 30 | COPY --from=ftpd_objects /MVSCE /MVSCE 31 | COPY --from=compiler /c/ftpd.load /XMI/ 32 | COPY source/build/ /XMI/ 33 | COPY FTPD.conf /XMI/ 34 | RUN rdrprep link_ftpd.template 35 | RUN python3 generate_install.py FTPD.conf 36 | RUN python3 -u autobuild.py -d -m /MVSCE 37 | RUN cp install.jcl \#001JCL.jcl 38 | RUN python3 -u /MVSCE/MVP/extras/package_release.py --xmi-files FTPD.XMI --task-files \#001JCL.jcl --mvsce /MVSCE --name ./FTPD.MVP 39 | WORKDIR /artifacts 40 | RUN mv /XMI/FTPD.XMI /artifacts && mv /XMI/install.jcl /artifacts && mv /XMI/FTPD.MVP /artifacts 41 | -------------------------------------------------------------------------------- /FTPD.MVP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MVS-sysgen/FTPD/83e80491df2895233de6aeb940aa692af61fcad1/FTPD.MVP -------------------------------------------------------------------------------- /FTPD.XMI: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MVS-sysgen/FTPD/83e80491df2895233de6aeb940aa692af61fcad1/FTPD.XMI -------------------------------------------------------------------------------- /FTPD.conf: -------------------------------------------------------------------------------- 1 | # FTPD Configuration file 2 | # These settings are used by the FTP server 3 | # Each setting may be overridden by using an equivalent PARM in the 4 | # FTPDPARM PROC as desribed in the README. 5 | # S FTPDPARM,SRVPORT=12345 6 | # Or directly in JCL: 7 | # //FTPD EXEC PGM=FTPDXCTL, 8 | # // PARM='SRVPORT=21021' 9 | # 10 | # To use a custom configuration file use the argument PARMLIB= 11 | # //FTPD EXEC PGM=FTPDXCTL, 12 | # // PARM='PARMLIB=SYS2.PARMLIB(FTPDPARM)' 13 | # 14 | # The default parmlib location is SYS1.PARMLIB(FTPDPM00) 15 | # 16 | ###################### 17 | # Settings # 18 | ###################### 19 | # 20 | # These settings can be in any order, including the DASD 21 | # 22 | # SRVPORT - The port that the FTPD server will listen on. 23 | SRVPORT=2121 24 | # SRVIP - The IP address of the hercules host machine to listen on. 25 | # The default is 'any' which is the equivalent of 'all' or 26 | # '0.0.0.0'. 27 | SRVIP=ANY 28 | # PASVADR - IP address to return for passive mode, comma separated. The 29 | # default is '127,0,0,1'. 30 | PASVADR=127,0,0,1 31 | # PASVPORTS - Port range for passive ports. Default is all ephmeral port 32 | # i.e. 1025-65535 33 | #PASVPORTS=22000-22200 34 | # INSECURE - By default the FTP server will only accept connections on 35 | # 127.0.0.1, set INSECURE=1 to allow connections from any IP 36 | #INSECURE=1 37 | # FAST - Uncomment this line to enable Library Optimisation Extensions 38 | #FAST=TRUE 39 | # The AUTHUSER can stop ftp daemon from a client session using 40 | # the "quote term" or 'quote terminate' FTP client command. 41 | AUTHUSER=IBMUSER 42 | # The FTP server will scan and read the VTOC of the following 43 | # DASD. The format is 'SERIAL,UNIT COMMENT'. You can copy the 44 | # the devices from SYS1.PARMLIB(VATLST00) and edit them to match 45 | # the entries below. 46 | MVSRES,3350 SYSTEM RESIDENCE (PRIVATE) 47 | MVS000,3350 SYSTEM DATASETS (PRIVATE) 48 | PUB000,3380 PUBLIC DATASETS (PRIVATE) 49 | PUB001,3390 PUBLIC DATASETS (PRIVATE) 50 | SYSCPK,3350 COMPILER/TOOLS (PRIVATE) 51 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # MVS 3.8J FTPD Server 2 | 3 | **Note**: This FTP server was originally written by Jason Winter and Juergen Winkelmann. 4 | 5 | This FTP daemon requires RAKF to run. You must install RAKF for this FTP server to work. 6 | 7 | ## Automated Install 8 | 9 | If you're using [MVS/CE](https://github.com/MVS-sysgen/sysgen) you can install this with 10 | the command `RX MVP INSTALL FTPD`. Once completed you can start the FTPD server on port 11 | 2121 in the hercules console with `/S FTPD`. 12 | 13 | :exclamation: If you're not using MVS/CE you should follow the manual install. 14 | 15 | ## Example Startup 16 | 17 | ``` 18 | /s ftpd 19 | / $HASP100 FTPD ON STCINRDR 20 | / $HASP373 FTPD STARTED 21 | / IEF403I FTPD - STARTED - TIME=23.05.01 22 | / +FTP000I Startup - FTPD Starting with 1 arguments 23 | / +FTP002I Startup - Reading parmlib SYS1.PARMLIB(FTPDPM00) 24 | / +FTP001I Startup - Parameter SRVPORT = '2121' 25 | / +FTP001I Startup - Parameter SRVIP = 'ANY' 26 | / +FTP001I Startup - Parameter PASVADR = '127,0,0,1' 27 | / +FTP001I Startup - Parameter AUTHUSER = 'IBMUSER' 28 | / +FTP001I Startup - Argument (1) DD Name AAINTRDR added 29 | / +FTP002I Startup - Reading 5 DASD 30 | / +FTP003I Startup - Reading DASD MVSRES,3350 31 | / +FTP003I Startup - Reading DASD MVS000,3350 32 | / +FTP003I Startup - Reading DASD PUB000,3380 33 | / +FTP003I Startup - Reading DASD PUB001,3390 34 | / +FTP003I Startup - Reading DASD SYSCPK,3350 35 | / +FTP004I Startup - 173 datasets 36 | / +FTP005I Startup Complete - FTP server listening on port: 2121 37 | ``` 38 | 39 | ## Access 40 | 41 | Using this install everyone in the RAKF group `ADMIN` has access to use FTP. To allow users 42 | access to FTP add the user or group to the **FTPAUTH** resource in the FACILITY class 43 | in `SYS1.SECURE.CNTL(PROFILES)`. Adding the example below would allow anyone in the 44 | USERS group access to FTP: 45 | 46 | ``` 47 | FACILITYFTPAUTH USERS READ 48 | ``` 49 | 50 | :exclamation: When adding RAKF profiles they must be in alphabetical order 51 | 52 | ## Operation 53 | 54 | **Startup/Shutdown** 55 | 56 | To start the FTPD server after IPL run the command `/s ftpd` in the hercules console. To stop the FTP 57 | server run the command `/p ftpd` or `/stop ftpd` on the hercules console. To make changes to the configuration (ports, IP etc) 58 | you can edit the config file `SYS1.PARMLIB(FTPDPM00)`. 59 | 60 | **Custom Configuration File** 61 | 62 | If you want to use a custom configuration file you can use edit the procedure `SYS2.PROCLIB(FTPD)` and replace this: 63 | 64 | ``` 65 | //FTPD EXEC PGM=FTPDXCTL,TIME=1440,REGION=8192K 66 | ``` 67 | 68 | with this, adding your own parmlib: 69 | 70 | ``` 71 | //FTPD EXEC PGM=FTPDXCTL,TIME=1440,REGION=8192K, 72 | // PARM='PARMLIB=PLACE.WITH.PARMLIBS(FTPPARMS)' 73 | ``` 74 | 75 | **FTPD Arguments** 76 | 77 | FTPD can take several arguments. These arguments override whatever the setting is in the configuration file and they 78 | can be in any order. To use them in JCL you use the `PARM=` field. For example, to use port 54321 and change the 79 | ip address the server listens on your JCL would look like: 80 | 81 | ``` 82 | //FTPD EXEC PGM=FTPDXCTL,TIME=1440,REGION=8192K, 83 | // PARM='SRVIP=192.168.0.5 SRVPORT=54321' 84 | ``` 85 | 86 | The following arguments are supported: 87 | 88 | - **SRVPORT** - The port that the FTPD server will listen on. 89 | - **SRVIP** - The IP address of the hercules host machine to listen on. The default is 'any' which is the equivalent of 'all' or '0.0.0.0'. 90 | - **PASVADR** - IP address to return for passive mode, comma separated. The default is '127,0,0,1'. This address is used only if getsockname doesn't return a suitable value. 91 | - **FAST** - When set to `TRUE` Library Optimisation Extensions are enabled 92 | - **AUTHUSR** - If set, this user can use `quote term`/`quote terminate` to shutdown the FTP server remotely 93 | - **DD** - The name of a *required* DD name in the JCL. Currently used for an internal reader. 94 | 95 | The procedue `FTPDPARM` has been installed which allows for the argument `SRVPORT` and `AUTHUSR`. 96 | 97 | ## Building from source 98 | 99 | See [BUILD.md](source/build/BUILD.md) for instructions how to build from source. 100 | 101 | ## Manual Installation 102 | 103 | ### Uploading/Installing needed files 104 | 105 | Upload the XMI file `FTPD.XMI` and receive the file with RECV370 (modify the JCL as needed). 106 | 107 | ```jcl 108 | //RECVFTPD JOB (TSO),'Recieve XMI',CLASS=A,MSGCLASS=A 109 | //* RECV370 DDNAMEs: 110 | //* ---------------- 111 | //* 112 | //* RECVLOG RECV370 output messages (required) 113 | //* 114 | //* RECVDBUG Optional, specifies debugging options. 115 | //* 116 | //* XMITIN input XMIT file to be received (required) 117 | //* 118 | //* SYSPRINT IEBCOPY output messages (required for DSORG=PO 119 | //* input datasets on SYSUT1) 120 | //* 121 | //* SYSUT1 Work dataset for IEBCOPY (not needed for sequential 122 | //* XMITs; required for partitioned XMITs) 123 | //* 124 | //* SYSUT2 Output dataset - sequential or partitioned 125 | //* 126 | //* SYSIN IEBCOPY input dataset (required for DSORG=PO XMITs) 127 | //* A DUMMY dataset. 128 | //* 129 | //RECV370 EXEC PGM=RECV370 130 | //STEPLIB DD DISP=SHR,DSN=SYSC.LINKLIB 131 | //* Change DSN to match where you uploaded the file 132 | //XMITIN DD DISP=SHR,DSN=USERNAME.FTPDLOAD.XMI 133 | //RECVLOG DD SYSOUT=* 134 | //SYSPRINT DD SYSOUT=* 135 | //SYSIN DD DUMMY 136 | //* Work temp dataset 137 | //SYSUT1 DD DSN=&&SYSUT1, 138 | // UNIT=VIO, 139 | // SPACE=(CYL,(5,1)), 140 | // DISP=(NEW,DELETE,DELETE) 141 | //* Output dataset 142 | //SYSUT2 DD DSN=SYSGEN.FTPD.LOADLIB, 143 | // UNIT=SYSALLDA,VOL=SER=PUB001, 144 | // SPACE=(CYL,(15,2,20),RLSE), 145 | // DISP=(NEW,CATLG,DELETE) 146 | ``` 147 | 148 | Inside the XMI file is `FTPD`, and `FTPDXCTL`, you need to copy them to your 149 | link library (most likely `SYS2.LINKLIB`): 150 | 151 | ```jcl 152 | //FTPXMIT1 JOB (TSO),'Recieve XMI',CLASS=A,MSGCLASS=A 153 | //STEP2CPY EXEC PGM=IEBCOPY 154 | //SYSPRINT DD SYSOUT=* 155 | //* Make sure the SYSUT2 DSN matches where you 156 | //* receive the XMI file above 157 | //SYSUT1 DD DSN=SYSGEN.FTPD.LOADLIB,DISP=SHR 158 | //SYSUT2 DD DSN=SYS2.LINKLIB,DISP=SHR 159 | //SYSIN DD * 160 | COPY INDD=((SYSUT1,R)),OUTDD=SYSUT2 161 | ``` 162 | 163 | Then install/place the following FTPD procedures in `SYS2.PROCLIB`: 164 | 165 | #### FTPD 166 | 167 | This launches FTPD with the config file located at `SYS1.PARMLIB(FTPDPM00)` (the default). 168 | 169 | ```jcl 170 | //FTPDPROC EXEC PGM=IEBGENER 171 | //SYSUT1 DD DATA,DLM=@@ 172 | //FTPD PROC 173 | //******************************************************************** 174 | //* 175 | //* MVS3.8j RAKF Enabled FTP server PROC 176 | //* To use: in Hercules console issue /s FTPD to start FTP server 177 | //* 178 | //* To change settings edit config file SYS1.PARMLIB(FTPDPM00) 179 | //* 180 | //******************************************************************** 181 | //FTPD EXEC PGM=FTPDXCTL,TIME=1440,REGION=4096K, 182 | // PARM='DD=AAINTRDR' 183 | //AAINTRDR DD SYSOUT=(A,INTRDR),DCB=(RECFM=FB,LRECL=80,BLKSIZE=80) 184 | //STDOUT DD SYSOUT=* 185 | @@ 186 | //SYSUT2 DD DISP=SHR,DSN=SYS2.PROCLIB(FTPD) 187 | //SYSPRINT DD SYSOUT=* 188 | //SYSIN DD DUMMY 189 | ``` 190 | 191 | #### FTPDPARM 192 | 193 | This launches FTPD with but allows the defaults and config file entries for `SRVPORT` and `AUTHUSER` to be overridden. For example, 194 | `/s ftpdparm,srvport=54321` 195 | 196 | ```jcl 197 | //FTPDPARM EXEC PGM=IEBGENER 198 | //SYSUT1 DD DATA,DLM=@@ 199 | //FTPDPARM PROC SRVPORT='2121',AUTHUSR='IBMUSER' 200 | //******************************************************************** 201 | //* 202 | //* MVS3.8j RAKF Enabled FTP server PROC with custom arguments 203 | //* To use: in Hercules console issue 204 | //* /s FTPDPARM,srvport=54321 205 | //* 206 | //******************************************************************** 207 | //FTPD EXEC PGM=FTPDXCTL,TIME=1440,REGION=4096K, 208 | // PARM='SRVPORT=&SRVPORT DD=AAINTRDR AUTHUSER=&AUTHUSR' 209 | //AAINTRDR DD SYSOUT=(A,INTRDR),DCB=(RECFM=FB,LRECL=80,BLKSIZE=80) 210 | //STDOUT DD SYSOUT=* 211 | @@ 212 | //SYSUT2 DD DISP=SHR,DSN=SYS2.PROCLIB(FTPDPARM) 213 | //SYSPRINT DD SYSOUT=* 214 | //SYSIN DD DUMMY 215 | ``` 216 | 217 | ### RAKF User 218 | 219 | Before proceeding you need to also make changes to RAKF and parmlibs 220 | 221 | You must add the FTPD user and group to RAKF in `SYS1.SECURE.CNTL(USERS)` 222 | 223 | :warning: make sure you pick a suitably random password not `RANDOM` 224 | 225 | :exclamation: RAKF users must be in alphabetical sort order 226 | 227 | ``` 228 | # User Group 229 | FTPD FTPD RANDOM N 230 | ``` 231 | 232 | Then update rakf from the hercules console with `/s rakfuser`. 233 | 234 | ### RAKF Profile 235 | 236 | You also need to approve the group FTPD to have access to SVC244 by 237 | adding the following to `SYS1.SECURE.CNTL(PROFILES)` 238 | 239 | ``` 240 | FACILITYSVC244 FTPD READ 241 | ``` 242 | 243 | You also need to add the `FTPAUTH` resource to the facility class. The 244 | lines below block access to anyone not in the ADMIN group. 245 | 246 | :exclamation: RAKF profiles must be in alphabetical order (i.e. FTPAUTH should follow DIAG8) 247 | 248 | ``` 249 | FACILITYFTPAUTH NONE 250 | FACILITYFTPAUTH ADMIN READ 251 | ``` 252 | 253 | Then update rakf from the hercules console with `/s rakfprof` 254 | 255 | ### SYS1.PARMLIB(FTPDPM00) 256 | 257 | Copy the file `FTPD.conf` to `SYS1.PARMLIB(FTPDPM00)` 258 | 259 | ### Launch FTPD 260 | 261 | You're all setup now you can launch FTPD with `/s ftpd` and read the messages on the hercules console 262 | 263 | 264 | ## Install JCL 265 | 266 | All of the above steps have been placed in the file `install.jcl`. Just make sure the datasets are correct 267 | and you can submit this JCL after uploading the XMI file. -------------------------------------------------------------------------------- /install.jcl: -------------------------------------------------------------------------------- 1 | //#001JCL JOB (FTPD), 2 | // 'FTPD INSTALL', 3 | // CLASS=A, 4 | // MSGCLASS=A, 5 | // REGION=8M, 6 | // MSGLEVEL=(1,1) 7 | //* 8 | //* Installs FTPD/FTPDXCTL to SYS2.LINKLIB 9 | //* Adds FTPDPM00 to SYS1.PARMLIB 10 | //* Adds FTPD procedure to SYS2.PROCLIB 11 | //* Adds the FTP user and updates RAKF profiles 12 | //* 13 | //FTDELETE EXEC PGM=IDCAMS,REGION=1024K 14 | //SYSPRINT DD SYSOUT=A 15 | //SYSIN DD * 16 | DELETE SYSGEN.FTPD.LOADLIB NONVSAM SCRATCH PURGE 17 | DELETE SYS2.PROCLIB(FTPD) 18 | DELETE SYS2.LINKLIB(FTPD) 19 | DELETE SYS2.LINKLIB(FTPDXCTL) 20 | /* IF THERE WAS NO DATASET TO DELETE, RESET CC */ 21 | IF LASTCC = 8 THEN 22 | DO 23 | SET LASTCC = 0 24 | SET MAXCC = 0 25 | END 26 | /* 27 | //* RECV370 DDNAMEs: 28 | //* ---------------- 29 | //* 30 | //* RECVLOG RECV370 output messages (required) 31 | //* 32 | //* RECVDBUG Optional, specifies debugging options. 33 | //* 34 | //* XMITIN input XMIT file to be received (required) 35 | //* 36 | //* SYSPRINT IEBCOPY output messages (required for DSORG=PO 37 | //* input datasets on SYSUT1) 38 | //* 39 | //* SYSUT1 Work dataset for IEBCOPY (not needed for sequential 40 | //* XMITs; required for partitioned XMITs) 41 | //* 42 | //* SYSUT2 Output dataset - sequential or partitioned 43 | //* 44 | //* SYSIN IEBCOPY input dataset (required for DSORG=PO XMITs) 45 | //* A DUMMY dataset. 46 | //* 47 | //RECV370 EXEC PGM=RECV370 48 | //STEPLIB DD DISP=SHR,DSN=SYSC.LINKLIB 49 | //XMITIN DD DSN=MVP.WORK(FTPD),DISP=SHR 50 | //RECVLOG DD SYSOUT=* 51 | //SYSPRINT DD SYSOUT=* 52 | //SYSIN DD DUMMY 53 | //* Work temp dataset 54 | //SYSUT1 DD DSN=&&SYSUT1, 55 | // UNIT=VIO, 56 | // SPACE=(CYL,(5,1)), 57 | // DISP=(NEW,DELETE,DELETE) 58 | //* Output dataset 59 | //SYSUT2 DD DSN=SYSGEN.FTPD.LOADLIB, 60 | // UNIT=SYSALLDA,VOL=SER=PUB001, 61 | // SPACE=(CYL,(15,2,20),RLSE), 62 | // DISP=(NEW,CATLG,DELETE) 63 | //* 64 | //* Copy FTPD/FTPDXCTL to SYS2.LINKLIB 65 | //* 66 | //STEP2CPY EXEC PGM=IEBCOPY 67 | //SYSPRINT DD SYSOUT=* 68 | //SYSUT1 DD DSN=SYSGEN.FTPD.LOADLIB,DISP=SHR 69 | //SYSUT2 DD DSN=SYS2.LINKLIB,DISP=SHR 70 | //SYSIN DD * 71 | COPY INDD=((SYSUT1,R)),OUTDD=SYSUT2 72 | /* 73 | //* 74 | //* Add FTPD to SYS2.PROCLIB 75 | //* 76 | //FTPDPROC EXEC PGM=IEBGENER 77 | //SYSUT1 DD DATA,DLM=@@ 78 | //FTPD PROC 79 | //******************************************************************** 80 | //* 81 | //* MVS3.8j RAKF Enabled FTP server PROC 82 | //* To use: in Hercules console issue /s FTPD to start FTP server 83 | //* 84 | //* To change settings edit config file SYS1.PARMLIB(FTPDPM00) 85 | //* 86 | //******************************************************************** 87 | //FTPD EXEC PGM=FTPDXCTL,TIME=1440,REGION=4096K, 88 | // PARM='DD=AAINTRDR' 89 | //AAINTRDR DD SYSOUT=(A,INTRDR),DCB=(RECFM=FB,LRECL=80,BLKSIZE=80) 90 | //STDOUT DD SYSOUT=* 91 | @@ 92 | //SYSUT2 DD DISP=SHR,DSN=SYS2.PROCLIB(FTPD) 93 | //SYSPRINT DD SYSOUT=* 94 | //SYSIN DD DUMMY 95 | //* Adds FTPDPARM 96 | //FTPDPARM EXEC PGM=IEBGENER 97 | //SYSUT1 DD DATA,DLM=@@ 98 | //FTPDPARM PROC SRVPORT='2121',AUTHUSR='IBMUSER' 99 | //******************************************************************** 100 | //* 101 | //* MVS3.8j RAKF Enabled FTP server PROC with custom arguments 102 | //* To use: in Hercules console issue 103 | //* /s FTPDPARM,srvport=54321,srvip=10.10.10.10 104 | //* 105 | //******************************************************************** 106 | //FTPD EXEC PGM=FTPDXCTL,TIME=1440,REGION=4096K, 107 | // PARM='SRVPORT=&SRVPORT DD=AAINTRDR AUTHUSR=&AUTHUSR' 108 | //AAINTRDR DD SYSOUT=(A,INTRDR),DCB=(RECFM=FB,LRECL=80,BLKSIZE=80) 109 | //STDOUT DD SYSOUT=* 110 | @@ 111 | //SYSUT2 DD DISP=SHR,DSN=SYS2.PROCLIB(FTPDPARM) 112 | //SYSPRINT DD SYSOUT=* 113 | //SYSIN DD DUMMY 114 | //* 115 | //* Add FTPDPM00 to SYS1.PARMLIB 116 | //* 117 | //FTPDDEVC EXEC PGM=IEBGENER 118 | //SYSUT1 DD DATA,DLM=@@ 119 | # FTPD Configuration file 120 | # These settings are used by the FTP server 121 | # Each setting may be overridden by using an equivalent PARM in the 122 | # FTPDPARM PROC as desribed in the README. 123 | # S FTPDPARM,SRVPORT=12345 124 | # Or directly in JCL: 125 | # //FTPD EXEC PGM=FTPDXCTL, 126 | # // PARM='SRVPORT=21021' 127 | # 128 | # To use a custom configuration file use the argument PARMLIB= 129 | # //FTPD EXEC PGM=FTPDXCTL, 130 | # // PARM='PARMLIB=SYS2.PARMLIB(FTPDPARM)' 131 | # 132 | # The default parmlib location is SYS1.PARMLIB(FTPDPM00) 133 | # 134 | ###################### 135 | # Settings # 136 | ###################### 137 | # 138 | # These settings can be in any order, including the DASD 139 | # 140 | # SRVPORT - The port that the FTPD server will listen on. 141 | SRVPORT=2121 142 | # SRVIP - The IP address of the hercules host machine to listen on. 143 | # The default is 'any' which is the equivalent of 'all' or 144 | # '0.0.0.0'. 145 | SRVIP=ANY 146 | # PASVADR - IP address to return for passive mode, comma separated. The 147 | # default is '127,0,0,1'. 148 | PASVADR=127,0,0,1 149 | # PASVPORTS - Port range for passive ports. Default is all ephmeral port 150 | # i.e. 1025-65535 151 | #PASVPORTS=22000-22200 152 | # INSECURE - By default the FTP server will only accept connections on 153 | # 127.0.0.1, set INSECURE=1 to allow connections from any IP 154 | #INSECURE=1 155 | # FAST - Uncomment this line to enable Library Optimisation Extensions 156 | #FAST=TRUE 157 | # The AUTHUSER can stop ftp daemon from a client session using 158 | # the "quote term" or 'quote terminate' FTP client command. 159 | AUTHUSER=IBMUSER 160 | # The FTP server will scan and read the VTOC of the following 161 | # DASD. The format is 'SERIAL,UNIT COMMENT'. You can copy the 162 | # the devices from SYS1.PARMLIB(VATLST00) and edit them to match 163 | # the entries below. 164 | MVSRES,3350 SYSTEM RESIDENCE (PRIVATE) 165 | MVS000,3350 SYSTEM DATASETS (PRIVATE) 166 | PUB000,3380 PUBLIC DATASETS (PRIVATE) 167 | PUB001,3390 PUBLIC DATASETS (PRIVATE) 168 | SYSCPK,3350 COMPILER/TOOLS (PRIVATE) 169 | @@ 170 | //SYSUT2 DD DISP=SHR,DSN=SYS1.PARMLIB(FTPDPM00) 171 | //SYSPRINT DD SYSOUT=* 172 | //SYSIN DD DUMMY 173 | //ADDRAKFR EXEC PGM=IEBGENER 174 | //SYSPRINT DD SYSOUT=* 175 | //SYSIN DD DUMMY 176 | //SYSUT1 DD * 177 | /* RAKF REXX SCRIPT ADD FTPD USER AND FACILITY */ 178 | 179 | call wto "FTPD Install: Adding FTPD user to RAKF" 180 | 181 | "ALLOC FI(USERS) DA('SYS1.SECURE.CNTL(USERS)') SHR " 182 | "EXECIO * DISKR USERS (FINIS STEM sortin." 183 | 184 | if rc > 0 then do 185 | say "Error reading SYS1.SECURE.CNTL(USERS):" rc 186 | "FREE F(USERS)" 187 | exit 8 188 | end 189 | 190 | not_already_installed = 1 191 | do i = 1 to sortin.0 192 | if pos('FTPD', sortin.i) > 0 then not_already_installed = 0 193 | end 194 | 195 | if not_already_installed then do 196 | call wto "FTPD Install: Creating new string" 197 | x = sortin.0 + 1 198 | user_group = LEFT("FTPD",9)||LEFT("FTPD",9) 199 | sortin.x = user_group||RANDOMPW(5)||" N" 200 | sortin.0 = x 201 | call wto "FTPD Install: Sorting" 202 | CALL RXSORT 203 | call wto "FTPD Install: writting to disk" 204 | "EXECIO * DISKW USERS (STEM SORTIN. OPEN FINIS" 205 | end 206 | "FREE F(USERS)" 207 | say "USERS Closed" 208 | 209 | call wto "FTPD Install: Adding FTPAUTH facility class" 210 | 211 | 212 | p1 = "FACILITYSVC244 FTPD READ" 213 | p2 = "FACILITYFTPAUTH NONE" 214 | p3 = "FACILITYFTPAUTH ADMIN READ" 215 | 216 | 217 | "ALLOC FI(PROFILES) DA('SYS1.SECURE.CNTL(PROFILES)') SHR " 218 | "EXECIO * DISKR PROFILES (FINIS STEM sortin." 219 | 220 | if rc > 0 then do 221 | say "Error reading SYS1.SECURE.CNTL(PROFILES):" rc 222 | "FREE F(PROFILES)" 223 | exit 8 224 | end 225 | not_already_installed = 1 226 | do i = 1 to sortin.0 227 | if pos(p1, sortin.i) > 0 then not_already_installed = 0 228 | if pos(p2, sortin.i) > 0 then not_already_installed = 0 229 | if pos(p3, sortin.i) > 0 then not_already_installed = 0 230 | end 231 | 232 | if not_already_installed then do 233 | x = sortin.0 + 1; sortin.x = p1 234 | x = sortin.0 + 2; sortin.x = p2 235 | x = sortin.0 + 3; sortin.x = p3 236 | sortin.0 = x 237 | 238 | CALL RXSORT 239 | "EXECIO * DISKW PROFILES (STEM SORTIN. OPEN FINIS" 240 | end 241 | 242 | "FREE F(PROFILES)" 243 | call wto "FTPD Users and profiles installed" 244 | /* 245 | //SYSUT2 DD DSN=SYS2.EXEC(RAKFUPDT),DISP=SHR 246 | //* ********************************************************** 247 | //EXECSORT EXEC PGM=IKJEFT01,REGION=8192K 248 | //SYSTSIN DD * 249 | FREE FILE(RXLIB) 250 | ALLOC FILE(RXLIB) DSN('BREXX.CURRENT.RXLIB') SHR 251 | FREE FILE(SYSEXEC) 252 | ALLOC FILE(SYSEXEC) DSN('SYS2.EXEC') SHR 253 | RX SYS2.EXEC(RAKFUPDT) 254 | //SYSTSPRT DD SYSOUT=* 255 | //RAKFUSER EXEC RAKFUSER 256 | //RAKFPROF EXEC RAKFPROF 257 | 258 | -------------------------------------------------------------------------------- /original/FTPD_PROC.jcl: -------------------------------------------------------------------------------- 1 | //FTPD PROC PASVADR='your,external,ip,addrs',SRVIP='any',SRVPORT=2121 2 | //******************************************************************** 3 | //* 4 | //* MVS3.8j RAKF Enabled FTP server PROC 5 | //* To use: in Hercules console issue /s FTPD to start FTP server on 6 | //* on port 2121 7 | //* 8 | //******************************************************************** 9 | //FTPD EXEC PGM=FTPDXCTL,TIME=1440,REGION=4096K, 10 | // PARM='&PASVADR &SRVPORT &SRVIP //DDN:AAINTRDR' 11 | //AAINTRDR DD SYSOUT=(A,INTRDR),DCB=(RECFM=FB,LRECL=80,BLKSIZE=80) 12 | //STDOUT DD SYSOUT=* -------------------------------------------------------------------------------- /original/MAKEFTPD.jcl: -------------------------------------------------------------------------------- 1 | //MAKEFTPD JOB (FTPD), 2 | // 'Make FTP Daemon', 3 | // CLASS=A,NOTIFY=JUERGEN, 4 | // MSGCLASS=J, 5 | // REGION=8M, 6 | // MSGLEVEL=(1,1) 7 | /*JOBPARM ROOM=JW 8 | //******************************************************************** 9 | //* 10 | //* Name: MAKEFTPD 11 | //* 12 | //* Desc: Assemble RAC support routines, compile and link FTPD 13 | //* 14 | //******************************************************************** 15 | //* 16 | //* assemble RAC support 17 | //* 18 | //FTPLOGIN EXEC ASMFC,PARM.ASM=(OBJ,NODECK),MAC1='SYS2.MACLIB', 19 | // MAC2='SYS1.AMODGEN' 20 | //ASM.SYSIN DD DSN=JUERGEN.FTPD.RAC.ASM(FTPLOGIN),DISP=SHR 21 | //ASM.SYSGO DD DSN=&&OBJ,DISP=(,PASS),SPACE=(TRK,3),UNIT=VIO, 22 | // DCB=(RECFM=FB,LRECL=80,BLKSIZE=3200) 23 | //FTPLGOUT EXEC ASMFC,PARM.ASM=(OBJ,NODECK),MAC1='SYS2.MACLIB', 24 | // MAC2='SYS1.AMODGEN' 25 | //ASM.SYSIN DD DSN=JUERGEN.FTPD.RAC.ASM(FTPLGOUT),DISP=SHR 26 | //ASM.SYSGO DD DSN=&&OBJ,DISP=(MOD,PASS) 27 | //FTPAUTH EXEC ASMFC,PARM.ASM=(OBJ,NODECK),MAC1='SYS2.MACLIB', 28 | // MAC2='SYS1.AMODGEN' 29 | //ASM.SYSIN DD DSN=JUERGEN.FTPD.RAC.ASM(FTPAUTH),DISP=SHR 30 | //ASM.SYSGO DD DSN=&&OBJ,DISP=(MOD,PASS) 31 | //FTPSU EXEC ASMFC,PARM.ASM=(OBJ,NODECK),MAC1='SYS2.MACLIB', 32 | // MAC2='SYS1.AMODGEN' 33 | //ASM.SYSIN DD DSN=JUERGEN.FTPD.RAC.ASM(FTPSU),DISP=SHR 34 | //ASM.SYSGO DD DSN=&&OBJ,DISP=(MOD,PASS) 35 | //O DD DISP=(,PASS),DSN=&&ALLOBJ,UNIT=SYSDA, 36 | // DCB=(RECFM=FB,LRECL=80,BLKSIZE=3200),SPACE=(TRK,(30,10,30)) 37 | //* 38 | //* ESD to XSD conversion for long names 39 | //* 40 | //SCAN EXEC PGM=OBJSCAN, 41 | // PARM='//DDN:I //DDN:N //DDN:O' 42 | //STEPLIB DD DSN=JCC.LINKLIB,DISP=SHR 43 | //STDOUT DD SYSOUT=* 44 | //I DD DSN=&&OBJ,DISP=(OLD,DELETE) 45 | //O DD DISP=(OLD,PASS),DSN=&&ALLOBJ(FTPDRAC) 46 | //N DD * 47 | FTPLOGIN rac_user_login 48 | FTPLGOUT rac_user_logout 49 | FTPAUTH rac_ftp_auth 50 | FTPSU rac_switch_user 51 | /* 52 | //* 53 | //* merge RAC support routines with JCC library 54 | //* 55 | //COPYCLIB EXEC PGM=IEBCOPY 56 | //SYSUT1 DD DISP=SHR,DSN=JCC.OBJ 57 | //SYSUT2 DD DISP=(OLD,PASS),DSN=&&ALLOBJ 58 | //SYSPRINT DD DUMMY 59 | //SYSIN DD DUMMY 60 | //* 61 | //* 62 | //* 63 | //COPYINDX EXEC PGM=IEBGENER 64 | //SYSUT1 DD DISP=SHR,DSN=JCC.OBJ(LIBLST) 65 | // DD * 66 | ftpdrac 67 | /* 68 | //SYSUT2 DD DISP=(OLD,PASS),DSN=&&ALLOBJ(LIBLST) 69 | //SYSPRINT DD DUMMY 70 | //SYSIN DD DUMMY 71 | //* 72 | //* compile and link FTPD 73 | //* 74 | //JCCCL EXEC JCCCL,INFILE='JUERGEN.FTPD.RAC.SRC(FTPD)', 75 | // OUTFILE='JUERGEN.FTPD.RAC.LOAD(FTPD)' 76 | //COMPILE.JCCINCS DD DISP=SHR,DSN=JUERGEN.FTPD.RAC.INCLUDE 77 | //PRELINK.L DD DSN=&&ALLOBJ,DISP=(OLD,DELETE) 78 | // 79 | -------------------------------------------------------------------------------- /original/MAKEXCTL.jcl: -------------------------------------------------------------------------------- 1 | //JUERGENA JOB (FTPD), 2 | // 'Make FTPDXCTL', 3 | // CLASS=A, 4 | // MSGCLASS=J, 5 | // REGION=8192K, 6 | // NOTIFY=JUERGEN, 7 | // MSGLEVEL=(1,1) 8 | //********************************************************************* 9 | //* 10 | //* Name: MAKEXCTL 11 | //* 12 | //* Desc: Assemble and link FTPD Wrapper 13 | //* 14 | //********************************************************************* 15 | //* 16 | //ASMCL EXEC ASMFCL,PARM.ASM=(OBJ,NODECK,NOXREF), 17 | // MAC1='SYS1.AMODGEN',MAC2='SYS2.MACLIB' 18 | //LKED.SYSLMOD DD DSN=SYS2.LINKLIB(FTPDXCTL),DISP=SHR 19 | //ASM.SYSIN DD * 20 | FTPDXCTL TITLE 'Set user and group for FTPD started task' 21 | *********************************************************************** 22 | *** *** 23 | *** Program: FTPDXCTL *** 24 | *** *** 25 | *** Purpose: Wrapper for FTPD started task to run *** 26 | *** using user/group FTPD/USER instead of STC/STCGROUP *** 27 | *** *** 28 | *** Usage: Replace FTPD in PGM parameter of EXEC statement with *** 29 | *** FTPDXCTL. *** 30 | *** *** 31 | *** Function: 1. Enter supervisor state. *** 32 | *** *** 33 | *** 2. Delete current security environment. *** 34 | *** *** 35 | *** 3. Create new security environment using FTPD/USER. *** 36 | *** *** 37 | *** 4. Return to problem state. *** 38 | *** *** 39 | *** 5. Pass control to FTPD via XCTL. *** 40 | *** *** 41 | *** Updates: 2015/03/03 original implementation. *** 42 | *** *** 43 | *** Author: Juergen Winkelmann, ETH Zuerich. *** 44 | *** *** 45 | *********************************************************************** 46 | PRINT NOGEN no expansions please 47 | FTPDXCTL CSECT , start of program 48 | SAVE (14,12),,* save registers 49 | LR R12,R15 establish module addressability 50 | USING FTPDXCTL,R12 tell assembler of base 51 | LA R2,SAVEA chain .. 52 | ST R13,4(,R2) .. the .. 53 | ST R2,8(,R13) .. save .. 54 | LR R13,R2 .. areas 55 | * 56 | * Enter supervisor state 57 | * 58 | BSPAUTH ON become authorized 59 | MODESET KEY=ZERO,MODE=SUP enter supervisor state 60 | BSPAUTH OFF no longer authorized 61 | * 62 | * switch to user FTPD and group USER 63 | * 64 | RACINIT ENVIR=DELETE delete and recreate RAC environment 65 | RACINIT ENVIR=CREATE,USERID=FTPD,GROUP=USER,PASSCHK=NO 66 | * 67 | * Return to problem state 68 | * 69 | MODESET KEY=NZERO,MODE=PROB back to problem state 70 | * 71 | * Pass control to FTPD 72 | * 73 | L R13,4(,R13) caller's save area pointer 74 | L R1,24(,R13) parameter list for FTPD 75 | XCTL (2,12),EP=FTPD execute FTPD, return never 76 | * 77 | * Data area 78 | * 79 | SAVEA DS 18F save area 80 | FTPD DC X'04',C'FTPD' new user 81 | USER DC X'04',C'USER' new group 82 | YREGS , register equates 83 | END FTPDXCTL end of FTPDXCTL 84 | -------------------------------------------------------------------------------- /original/Original_readme.txt: -------------------------------------------------------------------------------- 1 | RAC Based Authentication and Authorization for FTPD 2 | =================================================== 3 | 4 | As distributed with TK4- Update 07 the ftp daemon doesn't do any authentication 5 | or authorization checking on incoming connections. Each ftp session is granted 6 | full access to all datasets, which is how Jason Winter originally designed it. 7 | 8 | While this is very convenient for the typical "single user at home" TK4- system 9 | it is a major security risk for multi user systems being fully exposed to the 10 | internet. 11 | 12 | To be able to run the ftp daemon on internet accessible multi user systems at a 13 | reasonable risk, the most critical security weaknesses of Jason's original ftpd 14 | implementation have been hardened through a minimalistic integration into the 15 | MVS Resource Access Control (RAC) framework. To prevent unencrypted passwords 16 | from flowing across the internet it is recommended to use this RAC enhanced 17 | version of the ftp daemon together with a tunneling setup enforcing encryption 18 | on all incoming control connections. The resulting security level is then 19 | roughly comparable to using FTPS with encrypted control and clear text data 20 | paths on *i*x systems, which may well be regarded as conforming to the TCSEC C2 21 | level, if an appropriately configured security package (like IBM's RACF(tm) or 22 | its surrogate RAKF) is active on the MVS system operating the ftp daemon. 23 | Providing an encrypted data path, however, is not within the scope of this 24 | enhancement. 25 | 26 | 27 | 28 | Packaging 29 | +++++++++ 30 | 31 | This archive contains the following files and folders: 32 | 33 | README.txt -- This file. 34 | 35 | ftpd_rac.xmi -- An XMITted PDS containing six members, each of which in turn 36 | being an XMITted PDS: 37 | 38 | ASM -- Assembler source of the RAC interface modules. 39 | CNTL -- JCL to build the FTPD and the FTPDXCTL modules. 40 | INCLUDE -- C header library holding the RAC enhanced mvsdirs.h. 41 | LOAD -- loadlib containing ready to use FTPD and FTPDXCTL modules. 42 | PROC -- JCL procedure to execute the FTPD started task. 43 | SRC -- C source library holding the RAC enhanced ftpd.c. 44 | 45 | ftpd_rac.patch \ The RAC enhanced version in folder rac can be 46 | ftpd_rac_logout_0C4.patch / created by applying these two patches in sequence 47 | to the original version in folder std. 48 | 49 | Folder std: 50 | ----------- 51 | ftpd.c \ Source of the ftp daemon without RAC enhancements. This is the TK4- 52 | mvsdirs.h / adaption of Jason's original version. 53 | 54 | Folder rac: 55 | ----------- 56 | ftpd.c \ Source of the .. 57 | mvsdirs.h / .. RAC enhanced ftp daemon. 58 | 59 | Folders rac and std, and the two patch files are provided for documentation 60 | purposes only, to be able to trace the changes between the standard and the 61 | RAC enhanced version of the ftp daemon. Depending on the codepage in use and 62 | the system type (Windows, Linux, Unix) the source versions in folder rac might 63 | differ slightly from downloads of the versions in the INCLUDE and SRC libraries 64 | from ftpd_rac.xmi. If this is the case, the versions from ftpd_rac.xmi are to be 65 | considered the original versions. 66 | 67 | 68 | 69 | Installation 70 | ++++++++++++ 71 | 72 | To install the RAC enhanced ftp daemon on a standard TK4- or comparable MVS 3.8j 73 | system simply receive the LOAD PDS into SYS2.LINKLIB and the PROC PDS into 74 | SYS2.PROCLIB. The ASM, CNTL, INCLUDE and SRC datasets are required only if the 75 | the FTPD or FTPDXCTL module need to be rebuilt due to source changes. 76 | 77 | 78 | 79 | RAC Configuration 80 | +++++++++++++++++ 81 | 82 | o Create a user named FTPD in group USER. This user must have access to SVC 244 83 | allowing it to run authorized at any time. On TK4- this is accomplished by 84 | granting READ access to resource SVC244 in the FACILITY class. With this very 85 | critical permission this user has a high potential to put the whole system at 86 | risk. It should by all means be ensured that _no one_ ever can use this user 87 | to logon or to run batch jobs. So, choose a complex password, you will not 88 | need to remember it, as it will nowhere be used explicitly. In particular, 89 | this user does not need to be and thus should not be a TSO user. It must, when 90 | running unauthorized, be able to read the VTOC of all DASDs holding datasets 91 | and to list all catalogs containing datasets to be served by the ftp daemon. 92 | 93 | o Create a resource named FTPAUTH in the FACILITY class and give all users to 94 | be authorized to log in to the ftp daemon READ access to this resource. Users 95 | not having READ access to the FTPAUTH resource will not be allowed to log in 96 | even if they provide correct credentials. 97 | 98 | 99 | 100 | MVS Configuration and Operation 101 | +++++++++++++++++++++++++++++++ 102 | 103 | o Create SYS1.PARMLIB(VATLSTFF) using the same format as VATLST00. This list 104 | must contain the DASD volumes holding datasets to be served by the ftp daemon. 105 | Datasets on volumes not being in this list can neither be read nor written. 106 | Keep this list as small as possible in the interest of short root directory 107 | rebuild times. 108 | 109 | o Configure the parameters in SYS2.PROCLIB(FTPD) as described in the TCPIP TSO 110 | HELP member. Use a portnumber (SRVPORT parameter) above 1024 and set PASVADR 111 | to the comma separated IP address used by ftp clients to connect to. The SRVIP 112 | parameter should be set to any or to the same address used as PASVADR (dotted 113 | decimal format, however). 114 | 115 | The ftp daemon can now be started and stopped from the MVS console using the 116 | "S FTPD" and "P FTPD" commands, respectively. User HERC01 can also stop the 117 | ftp daemon from a client session using the "quote term" client command. This 118 | is hardcoded in the source; if another user is to be authorized for the term 119 | command, change the AUTH_USER preprocessor variable accordingly and rebuild 120 | the FTPD module. 121 | 122 | 123 | 124 | Hercules Host Configuration 125 | +++++++++++++++++++++++++++ 126 | 127 | The ftp daemon will not accept connections originating from interfaces it is 128 | not listening to. That means only connections originating on the host running 129 | the Hercules instance for MVS are possible. This is a security measure to 130 | enforce using tunnels for external connections, given that the ftp daemon itself 131 | isn't able to handle encrypted channels. 132 | 133 | To allow external connections an SSLv3 tunnel should be opened, forwarding the 134 | port ftp clients are connecting to to the port the ftp server is listening at. 135 | To accomplish this the freely available socat tool is recommended. Simply have 136 | the following command running in the backgroung on the Hercules host: 137 | 138 | socat OPENSSL-LISTEN:,bind=
,reuseaddr,fork, \ 139 | cert=,key=,verify=0, \ 140 | nodelay,max-children=0, \ 141 | keepalive,keepcnt=0,keepintvl=900 \ 142 | TCP4:
:,bind=
,nodelay, \ 143 | keepalive,keepcnt=0,keepintvl=900 \ 144 | 145 | where: 146 | 147 |
is the IP address of a local network interface on the Hercules host. 148 | All ftp clients will have to connect to this address. It must be 149 | identical to the address used in the PASVADR configuration parameter 150 | of the FTPD procedure on MVS. 151 | 152 | The port ftp clients will connect to. It must be different from the 153 | SRVPORT configuration parameter of the FTPD procedure. 154 | 155 | The port to which the tunnel will forward connections coming in 156 | on . It must be identical to the SRVPORT configuration 157 | parameter of the FTPD procedure. 158 | 159 | Name of the file holding the SSL certificate of the Hercules host. 160 | 161 | Name of the file holding the key to access the certificate. 162 | 163 | 164 | 165 | Usage 166 | +++++ 167 | 168 | o The control connection (aka the "ftp session") must be made using an SSLv3 169 | tunnel to port at address
as configured in the tunnel 170 | on the Hercules host. 171 | 172 | o Incoming connections must authenticate using the username and the password of 173 | a user having READ access to the FTPAUTH resource in the FACILITY class. 174 | 175 | o The ftp client must use passive mode for all transfers. 176 | 177 | 178 | Tunneling the Control Connection 179 | -------------------------------- 180 | 181 | Tunneling the control connection through SSLv3 means to create a tunnel 182 | "outside" the ftp protocol framework, through which a regular clear text 183 | connection is made. In particular, this is _not_ ftps (which negotiates the 184 | encryption as part of the ftp control session setup) and it is _not_ sftp 185 | which basically tunnels an "ftp like" session through ssh and as such has 186 | nothing to do at all with ftp. 187 | 188 | Many (if not most) ftp clients don't support tunneling the control connection 189 | as described above. In these cases it is necessary to explicitely create a 190 | tunnel before starting the ftp client. There exist quite a few tools to create 191 | an SSLv3 encrypted tunnel. One that works particularly well with FTPD is 192 | "socat", which is installed (or is available as a ready to install package) on 193 | most current Linux distributions. Windows and OS X versions can also be found 194 | on the internet. Instead of trying to find an ftp client that does the tunneling 195 | correctly it is recommended to install socat and use any regular clear text ftp 196 | client over it. 197 | 198 | Before connecting the first ftp client session issue the following socat 199 | command on the client system: 200 | 201 | socat TCP-LISTEN:,bind=127.0.0.1,reuseaddr,fork,nodelay \ 202 | OPENSSL:
:,nodelay,verify=0 203 | 204 | where is an arbitrary port on the client system to which the 205 | ftp client will connect, and
are as configured above 206 | on the Hercules host. Of course, DNS resolution is possible for
. 207 | 208 | Then the ftp session can be conducted as shown in the following example: 209 | 210 | $ ftp -n 127.0.0.1 211 | Connected to 127.0.0.1. 212 | 220 *** MVS38j FTP Daemon on TK4- *** 213 | ftp> user juergen 214 | 331 Okay, waiting for password. 215 | Password: 216 | 230 You are now logged in. 217 | Remote system type is MVS. 218 | ftp> cd /juergen.jcc.tcpip.src 219 | 250 CWD command successful 220 | ftp> passive 221 | Passive mode on. 222 | ftp> ls 223 | 227 Entering Passive Mode (129,132,252,105,194,193) 224 | 150 Now opening data connection 225 | total 00002 226 | -r-xr-xr-x 1 user group 1024 Mar 15 2015 FTPD-RAC 227 | -r-xr-xr-x 1 user group 1024 Mar 15 2015 FTPD-STD 228 | 226 Transfer complete! 229 | ftp> quit 230 | 221 Bye! 231 | 232 | 233 | 234 | Rebuilding from Source 235 | ++++++++++++++++++++++ 236 | 237 | 238 | FTPD 239 | ---- 240 | 241 | Much of the FTPD functionality is hardcoded in the source. So most probably it 242 | will at times be necessary to modify the source as found in the ASM, SRC and 243 | INCLUDE datasets of the XMIT distribution. To rebuild FTPD from source modify 244 | job MAKEFTPD in the CNTL dataset to point to the location where the datasets 245 | have been received to (replace JUERGEN.FTPD.RAC whith the HLQs used for receive) 246 | and submit the job. A return code of zero is expected for all steps. 247 | 248 | 249 | FTPDXCTL 250 | -------- 251 | 252 | FTPDXCTL is a wrapper called from the FTPD procedure. Its only purpose is to 253 | give up STC/STCGROUP in favour of FTPD/USER privileges before transfering 254 | control to the ftp daemon. The only thing that might need change here is the 255 | user (FTPD) and group (USER) the daemon is to run under in case FTPD/USER would 256 | not match local requirements. The FTPDXCTL module can be rebuilt by adapting job 257 | MAKEXCTL to match the locations used for receive and submitting the job. 258 | 259 | 260 | 261 | ---------- 262 | Juergen Winkelmann, 5/2/2016 263 | winkelmann@id.ethz.ch 264 | -------------------------------------------------------------------------------- /original/README.md: -------------------------------------------------------------------------------- 1 | ## Original Files 2 | 3 | These are some of the original files for the FTP server kept for historical reasons -------------------------------------------------------------------------------- /original/ftpd_rac.xmi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MVS-sysgen/FTPD/83e80491df2895233de6aeb940aa692af61fcad1/original/ftpd_rac.xmi -------------------------------------------------------------------------------- /source/README.md: -------------------------------------------------------------------------------- 1 | # FTPD Source Code 2 | 3 | These folders contain the source code for the RAKF enabled FTPD server for MVS 3.8j. 4 | 5 | ## Folders 6 | 7 | - **c** - contains the C source for the FTPD server itself 8 | - ftpd.c - Main FTPD daemon C code 9 | - mvsdirs.h - functions for managing MVS resources 10 | - **hlasm** - contains the ASM needed to support RAKF 11 | - FTPDXCTL - Wrapper for FTPD started task to run using user/group FTPD/USER instead of STC/STCGROUP. 12 | - FTPAUTH - C function to authorize or unauthorize FTPD. 13 | - FTPSU - C function to switch user. 14 | - FTPLOGIN - C function to process user login to the FTP service. 15 | - FTPLGOUT - C function to process FTP user logout. 16 | - **build** - contains instructions needed to build the FTPD server automatically and from scratch 17 | 18 | 19 | ## Building from source 20 | 21 | To build FTPD from sources (ASM and C) you can follow the instructions in [BUILD.md](build/BUILD.md). 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /source/build/BUILD.md: -------------------------------------------------------------------------------- 1 | # Building from source: 2 | 3 | ## Automated Build 4 | 5 | To make building easier an automated script `autobuild.py` was developed. 6 | It relies on the python automvs library. A docker build file has been created 7 | which automates all the steps below. 8 | 9 | To use it you can run the following commands from the root folder of this repo: 10 | 11 | 1) First build the FTPD XMI and install files: `docker build --tag ftpd:builder .` 12 | 2) Then mount the local folder inside the docker container: `docker run -it --entrypoint /bin/bash -v $(pwd):/project ftpd:builder` 13 | 3) Once inside the container copy the files to `/project`: `cp * /project` 14 | 15 | ## Before You Start 16 | 17 | :warning: Shell commands should all be run from withing the `FTPD/source/build/` folder. 18 | 19 | ### Enable TCPIP 20 | 21 | **Make sure you're running the latest SDL Hyperion and have enabled `HERC_TCPIP_EXTENSION` and `HERC_TCPIP_PROB_STATE` *before* you IPL** 22 | 23 | Place the following at the top of an RC file and launch hercules with: `hercules -r tcpip.rc` 24 | 25 | ``` 26 | facility enable HERC_TCPIP_EXTENSION 27 | facility enable HERC_TCPIP_PROB_STATE 28 | ``` 29 | 30 | Note: You do not need to do this if you are running MVS/CE. 31 | 32 | ### Install required MACLIBS 33 | 34 | If you are using MVS/CE building requires the MACLIBS package. Install with the TSO command `RX MVP INSTALL MACLIBS`. 35 | 36 | ### Installing rdrprep and JCC 37 | 1) Get JCC: `git clone https://github.com/mvslovers/jcc.git` 38 | 2) Get rdrprep: `git clone https://github.com/mvslovers/rdrprep.git` 39 | - Install rdrprep with `make` and `sudo make install` 40 | 41 | ## Compiling FTP 42 | 43 | 1) Generate new JCL to assemble the FTP hlasm programs:`python3 generate_ftpdrakf.py ../hlasm` 44 | - This will generate the file `assemble_ftprakf.jcl` which assembles all asm programs in [hlasm](../hlasm) 45 | - When we submit this job we it will output to the punch card writer 46 | - This step also assembles **FTPDXCTL** and places it in `SYS2.LINKLIB` 47 | 2) Change the punch output file and folder by typing the following on the hercules console `detach d` and enter followed by `attach d 3525 /path/to/FTPD/source/build/ftpdrakf.punch ebcdic` 48 | 3) Then submit `assemble_ftprakf.jcl` to the socket reader `cat assemble_ftprakf.jcl | ncat --send-only -w1 127.0.0.1 3505` 49 | - Each step should complete with `00000`. 50 | - When you see `/ $HASP150 MAKEFTPD ON PUNCH1 34 CARDS` in the hercules console type: `/$s punch1`, this will place the assembled binary in `/path/to/FTPD/source/build/ftpdrakf.punch` 51 | 4) Detach the punch card now in the hercules console: `detach d` 52 | 5) The punch writter as configured adds a seperator to the beginning and ends of files. To remove the `ftpdrakf.punch` header and footer you can use either of the following linux commands: 53 | - `dd if=ftpdrakf.punch bs=1 skip=160 count=2720 of=ftpdrac.pch` (where `count=` is the size of `ftpdrakf.punch` in bytes minus 240) 54 | - `tail -c +161 ftpdrakf.punch |head -c -80 > ftpdrac.pch` 55 | 6) Use `objscan` from jcc to replace HLASM names: `./jcc/objscan ftpdrakf.punch objscan_input.nam ftpdrac.obj` 56 | - This command replaced the labels/names in ASM like `FTPLOGIN` with `rac_user_login` which is used in `ftpd.c` 57 | - This step creates `ftpdrac.obj` 58 | 7) Compile `ftpd.c` with jcc: `./jcc/jcc -I./jcc/include -I../c -D__MVS_ -o -list=list.out ../c/ftpd.c` 59 | - It should complete with `JCC-RC:0` 60 | - This will create the file `ftpd.obj` 61 | - :exclamation: To enable debug output (which prints to SYSTOUT) add `-D__DEBUG__` to the command above after `-D__MVS_` 62 | 8) Use `prelink` to link the object: `./jcc/prelink -r jcc/objs ftpd.load ftpd.obj ftpdrac.obj` 63 | - It should completed with `PLK-RC:0` 64 | - This creates `ftpd.load` which is our assembled program ready to link in MVS 65 | 9) Generate an EBCDIC JCL file with the `ftpd.load` inside: `rdrprep link_ftpd.template` 66 | - This will create the file `reader.jcl` 67 | 10) Submit this job which will link and place `FTPD` in `SYS2.LINKLIB`: `cat reader.jcl | ncat --send-only -w1 localhost 3506` 68 | - The `LINKFTPD` step should complete with `00000`. 69 | 11) Generate and submit the install JCL which finalizes the install: `python3 generate_install.py ../../FTPD.conf && cat install.jcl | ncat --send-only -w1 127.0.0.1 3505` 70 | - You should only do this last step once, you wont need to do it again 71 | 72 | Congratulations, you compiled FTPD from scratch! 73 | 74 | Going forward if you only edit `ftpd.c`/`mvsdirs.h` you only need to do the **bottom 4 steps**! 75 | 76 | ### Launching FTPD 77 | 78 | Place the following JCL in `SYS2.PROCLIB(FTDDEV)` and run from the hercules console with `/s ftpddev` (it can be stopped with `/p ftpddev`): 79 | 80 | ```jcl 81 | //FTPDDEV PROC 82 | //******************************************************************** 83 | //* 84 | //* MVS3.8j RAKF Enabled FTP server PROC 85 | //* To use: in Hercules console issue /s FTPDDEV to start FTP server 86 | //* on the port configure in SYS1.PARMLIB(FTPDPM00) 87 | //* 88 | //******************************************************************** 89 | //FTPD EXEC PGM=FTPDXCTL,TIME=1440,REGION=8192K, 90 | // PARM='DD=AAINTRDR' 91 | //AAINTRDR DD SYSOUT=(A,INTRDR),DCB=(RECFM=FB,LRECL=80,BLKSIZE=80) 92 | //STDOUT DD SYSOUT=* 93 | ``` 94 | -------------------------------------------------------------------------------- /source/build/autobuild.py: -------------------------------------------------------------------------------- 1 | # this code will IPL and generate the object code needed to compile FTPD 2 | 3 | from pathlib import Path 4 | import sys 5 | from automvs import automation 6 | import logging 7 | import argparse 8 | import os 9 | 10 | cwd = os.getcwd() 11 | 12 | install_maclibs = '''//MVPMACLB JOB (FTPD), 13 | // 'Make FTP Daemon', 14 | // CLASS=A, 15 | // MSGCLASS=A, 16 | // REGION=8M, 17 | // MSGLEVEL=(1,1), 18 | // USER=IBMUSER,PASSWORD=SYS1 19 | //* The build requires the MACLIB package 20 | //MVPINST EXEC MVP,INSTALL='MACLIB -D' 21 | ''' 22 | 23 | desc = 'FTP Autobuild Script' 24 | arg_parser = argparse.ArgumentParser(description=desc) 25 | arg_parser.add_argument('-d', '--debug', help="Print debugging statements", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.WARNING) 26 | arg_parser.add_argument('-m', '--mvsce', help="MVS/CE folder location", default="MVSCE") 27 | arg_parser.add_argument('--objects',help="Builds HLASM instead of XMI file", action="store_true") 28 | args = arg_parser.parse_args() 29 | 30 | builder = automation(mvsce=args.mvsce,loglevel=args.loglevel) 31 | try: 32 | if args.objects: 33 | builder.ipl(clpa=False) 34 | builder.submit(install_maclibs) 35 | builder.wait_for_job("MVPMACLB") 36 | builder.check_maxcc("MVPMACLB") 37 | builder.send_oper("$s punch1") 38 | builder.wait_for_string("$HASP000 OK") 39 | with open("{}/assemble_ftprakf.jcl".format(cwd), "r") as infile: 40 | builder.submit(infile.read()) 41 | builder.wait_for_string("$HASP190 MAKEFTPD SETUP -- PUNCH1 -- F = STD1") 42 | builder.send_oper("$s punch1") 43 | builder.wait_for_string("HASP250 MAKEFTPD IS PURGED") 44 | builder.check_maxcc("MAKEFTPD") 45 | else: 46 | builder.ipl(clpa=False) 47 | builder.send_oper("$s punch1") 48 | builder.wait_for_string("$HASP000 OK") 49 | with open("{}/reader.jcl".format(cwd), "rb") as infile: 50 | builder.submit(infile.read(),port=3506,ebcdic=True) 51 | builder.wait_for_string("$HASP190 LINKFTPD SETUP -- PUNCH1 -- F = STD1") 52 | builder.send_oper("$s punch1") 53 | builder.wait_for_string("HASP250 LINKFTPD IS PURGED") 54 | builder.check_maxcc("LINKFTPD") 55 | 56 | finally: 57 | builder.quit_hercules() 58 | 59 | with open("punchcards/pch00d.txt", 'rb') as punchfile: 60 | punchfile.seek(160) 61 | no_headers = punchfile.read() 62 | no_footers = no_headers[:-80] 63 | 64 | os.chdir(cwd) 65 | 66 | 67 | if args.objects: 68 | with open("ftpdrakf.punch", 'wb') as objects_out: 69 | objects_out.write(no_footers) 70 | else: 71 | with open("FTPD.XMI", 'wb') as xmi_out: 72 | xmi_out.write(no_footers) 73 | -------------------------------------------------------------------------------- /source/build/generate_ftpdrakf.py: -------------------------------------------------------------------------------- 1 | # generates the JCL needed to build the HLASM files 2 | 3 | from pathlib import Path 4 | import sys 5 | 6 | if len(sys.argv) == 1: 7 | print("usage: {} /path/to/FTPD/hlasm".format(sys.argv[0])) 8 | sys.exit(-1) 9 | 10 | 11 | JCL_header = '''//MAKEFTPD JOB (FTPD), 12 | // 'Make FTP Daemon', 13 | // CLASS=A, 14 | // MSGCLASS=A, 15 | // REGION=8M, 16 | // MSGLEVEL=(1,1), 17 | // USER=IBMUSER,PASSWORD=SYS1 18 | //********************************************************************* 19 | //* 20 | //* Name: MAKEXCTL 21 | //* 22 | //* Desc: Assemble and link FTPD Wrapper 23 | //* 24 | //********************************************************************* 25 | //* 26 | //* Create temp PDS for output to punchcard writer 27 | //* 28 | //MAKEOBJ EXEC PGM=IEFBR14 29 | //OUTPUT DD DSN=&&OBJ,DISP=(,PASS),SPACE=(TRK,3),UNIT=VIO, 30 | // DCB=(RECFM=FB,LRECL=80,BLKSIZE=3200) 31 | //* 32 | //* Assemble/link FTPDXCTL to SYS2.LINKLIB 33 | //* 34 | //ASMCL EXEC ASMFCL,PARM.ASM=(OBJ,NODECK,NOXREF), 35 | // MAC1='SYS1.AMODGEN',MAC2='SYS2.MACLIB' 36 | //ASM.SYSIN DD * 37 | {FTPDXCTL} 38 | //LKED.SYSLMOD DD DSN=SYS2.LINKLIB(FTPDXCTL),DISP=SHR 39 | //* 40 | //* ******************************** 41 | //* Make sure the punchcard writer on device D (Class B) 42 | //* is reset 43 | //* 44 | //* ******************************** 45 | //* 46 | ''' 47 | 48 | assemble_objects = '''//* Assemble {filename} 49 | //* 50 | //{filename:<8} EXEC ASMFC,PARM.ASM=(OBJ,NODECK),MAC1='SYS2.MACLIB', 51 | // MAC2='SYS1.AMODGEN' 52 | //ASM.SYSIN DD * 53 | {file_contents} 54 | /* 55 | //ASM.SYSGO DD DSN=&&OBJ,DISP=(MOD,PASS) 56 | //* 57 | ''' 58 | 59 | jcl_footer = '''//* Now to output the temp dataset &&OBJ to Class B which is the 60 | //* punch out (pch00d.txt or changed to ftpdrakf.punch using) 61 | //PUNCHOUT EXEC PGM=IEBGENER 62 | //SYSIN DD DUMMY 63 | //SYSUT1 DD DSN=&&OBJ,DISP=SHR 64 | //SYSUT2 DD SYSOUT=B 65 | //SYSPRINT DD SYSOUT=* 66 | ''' 67 | 68 | p = Path(sys.argv[1]).glob('**/*.hlasm') 69 | print("*** Parsing files in {}".format(sys.argv[1])) 70 | files = [x for x in p if x.is_file()] 71 | asm = '' 72 | for asm_file in sorted(files): 73 | print("*** File: {}".format(asm_file)) 74 | if "FTPDXCTL" in str(asm_file): 75 | with open (asm_file,"r") as FTPDXCTL: 76 | header = JCL_header.format(FTPDXCTL=FTPDXCTL.read().rstrip()) 77 | else: 78 | 79 | with open (asm_file,"r") as objectasm: 80 | asm += assemble_objects.format(filename=asm_file.stem, file_contents=objectasm.read().rstrip()) 81 | 82 | print("*** Writting: assemble_ftprakf.jcl") 83 | with open ("assemble_ftprakf.jcl","w") as outfile: 84 | outfile.write(header+asm+jcl_footer.rstrip()) -------------------------------------------------------------------------------- /source/build/generate_install.py: -------------------------------------------------------------------------------- 1 | # generates the JCL needed to build the HLASM files 2 | 3 | import sys 4 | import random 5 | import string 6 | 7 | if len(sys.argv) == 1: 8 | print("usage: {} /path/to/FTPD.conf".format(sys.argv[0])) 9 | sys.exit(-1) 10 | 11 | def randomStringwithDigitsAndSymbols(stringLength=10): 12 | """Generate a random string of letters, digits and special characters """ 13 | password_characters = string.ascii_letters + string.digits + string.punctuation 14 | return ''.join(random.choice(password_characters) for i in range(stringLength)) 15 | 16 | 17 | install_jcl = '''//#001JCL JOB (FTPD), 18 | // 'FTPD INSTALL', 19 | // CLASS=A, 20 | // MSGCLASS=A, 21 | // REGION=8M, 22 | // MSGLEVEL=(1,1) 23 | //* 24 | //* Installs FTPD/FTPDXCTL to SYS2.LINKLIB 25 | //* Adds FTPDPM00 to SYS1.PARMLIB 26 | //* Adds FTPD procedure to SYS2.PROCLIB 27 | //* Adds the FTP user and updates RAKF profiles 28 | //* 29 | //FTDELETE EXEC PGM=IDCAMS,REGION=1024K 30 | //SYSPRINT DD SYSOUT=A 31 | //SYSIN DD * 32 | DELETE SYSGEN.FTPD.LOADLIB NONVSAM SCRATCH PURGE 33 | DELETE SYS2.PROCLIB(FTPD) 34 | DELETE SYS2.LINKLIB(FTPD) 35 | DELETE SYS2.LINKLIB(FTPDXCTL) 36 | /* IF THERE WAS NO DATASET TO DELETE, RESET CC */ 37 | IF LASTCC = 8 THEN 38 | DO 39 | SET LASTCC = 0 40 | SET MAXCC = 0 41 | END 42 | /* 43 | //* RECV370 DDNAMEs: 44 | //* ---------------- 45 | //* 46 | //* RECVLOG RECV370 output messages (required) 47 | //* 48 | //* RECVDBUG Optional, specifies debugging options. 49 | //* 50 | //* XMITIN input XMIT file to be received (required) 51 | //* 52 | //* SYSPRINT IEBCOPY output messages (required for DSORG=PO 53 | //* input datasets on SYSUT1) 54 | //* 55 | //* SYSUT1 Work dataset for IEBCOPY (not needed for sequential 56 | //* XMITs; required for partitioned XMITs) 57 | //* 58 | //* SYSUT2 Output dataset - sequential or partitioned 59 | //* 60 | //* SYSIN IEBCOPY input dataset (required for DSORG=PO XMITs) 61 | //* A DUMMY dataset. 62 | //* 63 | //RECV370 EXEC PGM=RECV370 64 | //STEPLIB DD DISP=SHR,DSN=SYSC.LINKLIB 65 | //XMITIN DD DSN=MVP.WORK(FTPD),DISP=SHR 66 | //RECVLOG DD SYSOUT=* 67 | //SYSPRINT DD SYSOUT=* 68 | //SYSIN DD DUMMY 69 | //* Work temp dataset 70 | //SYSUT1 DD DSN=&&SYSUT1, 71 | // UNIT=VIO, 72 | // SPACE=(CYL,(5,1)), 73 | // DISP=(NEW,DELETE,DELETE) 74 | //* Output dataset 75 | //SYSUT2 DD DSN=SYSGEN.FTPD.LOADLIB, 76 | // UNIT=SYSALLDA,VOL=SER=PUB001, 77 | // SPACE=(CYL,(15,2,20),RLSE), 78 | // DISP=(NEW,CATLG,DELETE) 79 | //* 80 | //* Copy FTPD/FTPDXCTL to SYS2.LINKLIB 81 | //* 82 | //STEP2CPY EXEC PGM=IEBCOPY 83 | //SYSPRINT DD SYSOUT=* 84 | //SYSUT1 DD DSN=SYSGEN.FTPD.LOADLIB,DISP=SHR 85 | //SYSUT2 DD DSN=SYS2.LINKLIB,DISP=SHR 86 | //SYSIN DD * 87 | COPY INDD=((SYSUT1,R)),OUTDD=SYSUT2 88 | /* 89 | //* 90 | //* Add FTPD to SYS2.PROCLIB 91 | //* 92 | //FTPDPROC EXEC PGM=IEBGENER 93 | //SYSUT1 DD DATA,DLM=@@ 94 | //FTPD PROC 95 | //******************************************************************** 96 | //* 97 | //* MVS3.8j RAKF Enabled FTP server PROC 98 | //* To use: in Hercules console issue /s FTPD to start FTP server 99 | //* 100 | //* To change settings edit config file SYS1.PARMLIB(FTPDPM00) 101 | //* 102 | //******************************************************************** 103 | //FTPD EXEC PGM=FTPDXCTL,TIME=1440,REGION=4096K, 104 | // PARM='DD=AAINTRDR' 105 | //AAINTRDR DD SYSOUT=(A,INTRDR),DCB=(RECFM=FB,LRECL=80,BLKSIZE=80) 106 | //STDOUT DD SYSOUT=* 107 | @@ 108 | //SYSUT2 DD DISP=SHR,DSN=SYS2.PROCLIB(FTPD) 109 | //SYSPRINT DD SYSOUT=* 110 | //SYSIN DD DUMMY 111 | //* Adds FTPDPARM 112 | //FTPDPARM EXEC PGM=IEBGENER 113 | //SYSUT1 DD DATA,DLM=@@ 114 | //FTPDPARM PROC SRVPORT='2121',AUTHUSR='IBMUSER' 115 | //******************************************************************** 116 | //* 117 | //* MVS3.8j RAKF Enabled FTP server PROC with custom arguments 118 | //* To use: in Hercules console issue 119 | //* /s FTPDPARM,srvport=54321,srvip=10.10.10.10 120 | //* 121 | //******************************************************************** 122 | //FTPD EXEC PGM=FTPDXCTL,TIME=1440,REGION=4096K, 123 | // PARM='SRVPORT=&SRVPORT DD=AAINTRDR AUTHUSR=&AUTHUSR' 124 | //AAINTRDR DD SYSOUT=(A,INTRDR),DCB=(RECFM=FB,LRECL=80,BLKSIZE=80) 125 | //STDOUT DD SYSOUT=* 126 | @@ 127 | //SYSUT2 DD DISP=SHR,DSN=SYS2.PROCLIB(FTPDPARM) 128 | //SYSPRINT DD SYSOUT=* 129 | //SYSIN DD DUMMY 130 | //* 131 | //* Add FTPDPM00 to SYS1.PARMLIB 132 | //* 133 | //FTPDDEVC EXEC PGM=IEBGENER 134 | //SYSUT1 DD DATA,DLM=@@ 135 | {ftpd_conf} 136 | @@ 137 | //SYSUT2 DD DISP=SHR,DSN=SYS1.PARMLIB(FTPDPM00) 138 | //SYSPRINT DD SYSOUT=* 139 | //SYSIN DD DUMMY 140 | //ADDRAKFR EXEC PGM=IEBGENER 141 | //SYSPRINT DD SYSOUT=* 142 | //SYSIN DD DUMMY 143 | //SYSUT1 DD * 144 | /* RAKF REXX SCRIPT ADD FTPD USER AND FACILITY */ 145 | 146 | call wto "FTPD Install: Adding FTPD user to RAKF" 147 | 148 | "ALLOC FI(USERS) DA('SYS1.SECURE.CNTL(USERS)') SHR " 149 | "EXECIO * DISKR USERS (FINIS STEM sortin." 150 | 151 | if rc > 0 then do 152 | say "Error reading SYS1.SECURE.CNTL(USERS):" rc 153 | "FREE F(USERS)" 154 | exit 8 155 | end 156 | 157 | not_already_installed = 1 158 | do i = 1 to sortin.0 159 | if pos('FTPD', sortin.i) > 0 then not_already_installed = 0 160 | end 161 | 162 | if not_already_installed then do 163 | call wto "FTPD Install: Creating new string" 164 | x = sortin.0 + 1 165 | user_group = LEFT("FTPD",9)||LEFT("FTPD",9) 166 | sortin.x = user_group||RANDOMPW(5)||" N" 167 | sortin.0 = x 168 | call wto "FTPD Install: Sorting" 169 | CALL RXSORT 170 | call wto "FTPD Install: writting to disk" 171 | "EXECIO * DISKW USERS (STEM SORTIN. OPEN FINIS" 172 | end 173 | "FREE F(USERS)" 174 | say "USERS Closed" 175 | 176 | call wto "FTPD Install: Adding FTPAUTH facility class" 177 | 178 | 179 | p1 = "FACILITYSVC244 FTPD READ" 180 | p2 = "FACILITYFTPAUTH NONE" 181 | p3 = "FACILITYFTPAUTH ADMIN READ" 182 | 183 | 184 | "ALLOC FI(PROFILES) DA('SYS1.SECURE.CNTL(PROFILES)') SHR " 185 | "EXECIO * DISKR PROFILES (FINIS STEM sortin." 186 | 187 | if rc > 0 then do 188 | say "Error reading SYS1.SECURE.CNTL(PROFILES):" rc 189 | "FREE F(PROFILES)" 190 | exit 8 191 | end 192 | not_already_installed = 1 193 | do i = 1 to sortin.0 194 | if pos(p1, sortin.i) > 0 then not_already_installed = 0 195 | if pos(p2, sortin.i) > 0 then not_already_installed = 0 196 | if pos(p3, sortin.i) > 0 then not_already_installed = 0 197 | end 198 | 199 | if not_already_installed then do 200 | x = sortin.0 + 1; sortin.x = p1 201 | x = sortin.0 + 2; sortin.x = p2 202 | x = sortin.0 + 3; sortin.x = p3 203 | sortin.0 = x 204 | 205 | CALL RXSORT 206 | "EXECIO * DISKW PROFILES (STEM SORTIN. OPEN FINIS" 207 | end 208 | 209 | "FREE F(PROFILES)" 210 | call wto "FTPD Users and profiles installed" 211 | /* 212 | //SYSUT2 DD DSN=SYS2.EXEC(RAKFUPDT),DISP=SHR 213 | //* ********************************************************** 214 | //EXECSORT EXEC PGM=IKJEFT01,REGION=8192K 215 | //SYSTSIN DD * 216 | FREE FILE(RXLIB) 217 | ALLOC FILE(RXLIB) DSN('BREXX.CURRENT.RXLIB') SHR 218 | FREE FILE(SYSEXEC) 219 | ALLOC FILE(SYSEXEC) DSN('SYS2.EXEC') SHR 220 | RX SYS2.EXEC(RAKFUPDT) 221 | //SYSTSPRT DD SYSOUT=* 222 | //RAKFUSER EXEC RAKFUSER 223 | //RAKFPROF EXEC RAKFPROF 224 | 225 | ''' 226 | 227 | with open(sys.argv[1],"r") as infile: 228 | with open ("install.jcl","w") as outfile: 229 | outfile.write(install_jcl.format(ftpd_conf=infile.read().rstrip())) -------------------------------------------------------------------------------- /source/build/link_ftpd.template: -------------------------------------------------------------------------------- 1 | //LINKFTPD JOB (FTPD), 2 | // 'Make FTP Daemon', 3 | // CLASS=A, 4 | // MSGCLASS=A, 5 | // REGION=8M, 6 | // MSGLEVEL=(1,1), 7 | // USER=IBMUSER,PASSWORD=SYS1 8 | //LKED EXEC PGM=IEWL,PARM='NCAL,MAP,LIST,XREF,NORENT' 9 | //SYSUT1 DD UNIT=SYSDA,SPACE=(CYL,(5,2)) 10 | //SYSPRINT DD SYSOUT=* 11 | //SYSLMOD DD DSN=SYS2.LINKLIB(FTPD),DISP=SHR 12 | //SYSLIN DD DATA,DLM=$$ 13 | ::E ftpd.load 14 | $$ 15 | //* 16 | //* ------------------------------------------------------------------ 17 | //* DELETE DATASETS IF ALREADY CREATED 18 | //* ------------------------------------------------------------------ 19 | //* 20 | //FTDELETE EXEC PGM=IDCAMS,REGION=1024K 21 | //SYSPRINT DD SYSOUT=A 22 | //SYSIN DD * 23 | DELETE SYSGEN.FTPD.LINKLIB NONVSAM SCRATCH PURGE 24 | DELETE SYSGEN.FTPD.XMIT NONVSAM SCRATCH PURGE 25 | /* IF THERE WAS NO DATASET TO DELETE, RESET CC */ 26 | IF LASTCC = 8 THEN 27 | DO 28 | SET LASTCC = 0 29 | SET MAXCC = 0 30 | END 31 | /* 32 | //* 33 | //* ------------------------------------------------------------------ 34 | //* COPY FTPD and FTPD to SYSGEN.FTPD.LINKLIB 35 | //* ------------------------------------------------------------------ 36 | //* 37 | //COPYLL EXEC PGM=IEBCOPY 38 | //SYSPRINT DD SYSOUT=A 39 | //SYSUT1 DD DSN=SYS2.LINKLIB,DISP=SHR 40 | //SYSUT2 DD DSN=SYSGEN.FTPD.LINKLIB,DISP=(,CATLG,DELETE), 41 | // UNIT=SYSDA,VOL=SER=PUB001,SPACE=(TRK,(20,,2)), 42 | // DCB=(RECFM=U,LRECL=0,BLKSIZE=19069) 43 | //SYSIN DD * 44 | COPY OUTDD=SYSUT2,INDD=SYSUT1 45 | SELECT MEMBER=FTPD 46 | SELECT MEMBER=FTPDXCTL 47 | /* 48 | //* 49 | //* ------------------------------------------------------------------ 50 | //* GENERATE XMIT SYSGEN.FTPD.XMIT using SYSGEN.FTPD.LINKLIB 51 | //* ------------------------------------------------------------------ 52 | //* 53 | //XMITLLIB EXEC PGM=XMIT370 54 | //STEPLIB DD DSN=SYSC.LINKLIB,DISP=SHR 55 | //XMITLOG DD SYSOUT=* 56 | //XMITOUT DD SYSOUT=B 57 | //SYSPRINT DD SYSOUT=* 58 | //SYSIN DD DUMMY 59 | //SYSUT1 DD DSN=SYSGEN.FTPD.LINKLIB,DISP=SHR 60 | //SYSUT2 DD DSN=&&SYSUT2,UNIT=SYSDA, 61 | // SPACE=(TRK,(255,255)), 62 | // DISP=(NEW,DELETE,DELETE) 63 | //* 64 | //* Add FTPD to SYS2.PROCLIB 65 | //* 66 | //FTPDPROC EXEC PGM=IEBGENER 67 | //SYSUT1 DD DATA,DLM=@@ 68 | //FTPD PROC 69 | //******************************************************************** 70 | //* 71 | //* MVS3.8j RAKF Enabled FTP server PROC 72 | //* To use: in Hercules console issue /s FTPD to start FTP server 73 | //* 74 | //* To change settings edit config file SYS1.PARMLIB(FTPDPM00) 75 | //* 76 | //******************************************************************** 77 | //FTPD EXEC PGM=FTPDXCTL,TIME=1440,REGION=4096K, 78 | // PARM='DD=AAINTRDR' 79 | //AAINTRDR DD SYSOUT=(A,INTRDR),DCB=(RECFM=FB,LRECL=80,BLKSIZE=80) 80 | //STDOUT DD SYSOUT=* 81 | @@ 82 | //SYSUT2 DD DISP=SHR,DSN=SYS2.PROCLIB(FTPD) 83 | //SYSPRINT DD SYSOUT=* 84 | //SYSIN DD DUMMY 85 | //* Adds FTPDPARM 86 | //FTPDPARM EXEC PGM=IEBGENER 87 | //SYSUT1 DD DATA,DLM=@@ 88 | //FTPDPARM PROC SRVPORT='2121',AUTHUSR='IBMUSER' 89 | //******************************************************************** 90 | //* 91 | //* MVS3.8j RAKF Enabled FTP server PROC with custom arguments 92 | //* To use: in Hercules console issue 93 | //* /s FTPDPARM,srvport=54321,srvip=10.10.10.10 94 | //* 95 | //******************************************************************** 96 | //FTPD EXEC PGM=FTPDXCTL,TIME=1440,REGION=4096K, 97 | // PARM='SRVPORT=&SRVPORT DD=AAINTRDR AUTHUSR=&AUTHUSR' 98 | //AAINTRDR DD SYSOUT=(A,INTRDR),DCB=(RECFM=FB,LRECL=80,BLKSIZE=80) 99 | //STDOUT DD SYSOUT=* 100 | @@ 101 | //SYSUT2 DD DISP=SHR,DSN=SYS2.PROCLIB(FTPDPARM) 102 | //SYSPRINT DD SYSOUT=* 103 | //SYSIN DD DUMMY 104 | //* 105 | //* Add FTPDPM00 to SYS1.PARMLIB 106 | //* 107 | //FTPDDEVC EXEC PGM=IEBGENER 108 | //SYSUT1 DD DATA,DLM=@@ 109 | ::A FTPD.conf 110 | @@ 111 | //SYSUT2 DD DISP=SHR,DSN=SYS1.PARMLIB(FTPDPM00) 112 | //SYSPRINT DD SYSOUT=* 113 | //SYSIN DD DUMMY 114 | //ADDRAKFR EXEC PGM=IEBGENER 115 | //SYSPRINT DD SYSOUT=* 116 | //SYSIN DD DUMMY 117 | //SYSUT1 DD * 118 | /* RAKF REXX SCRIPT ADD FTPD USER AND FACILITY */ 119 | 120 | call wto "FTPD Install: Adding FTPD user to RAKF" 121 | 122 | "ALLOC FI(USERS) DA('SYS1.SECURE.CNTL(USERS)') SHR " 123 | "EXECIO * DISKR USERS (FINIS STEM sortin." 124 | 125 | if rc > 0 then do 126 | say "Error reading SYS1.SECURE.CNTL(USERS):" rc 127 | "FREE F(USERS)" 128 | exit 8 129 | end 130 | 131 | not_already_installed = 1 132 | do i = 1 to sortin.0 133 | if pos('FTPD', sortin.i) > 0 then not_already_installed = 0 134 | end 135 | 136 | if not_already_installed then do 137 | call wto "FTPD Install: Creating new string" 138 | x = sortin.0 + 1 139 | user_group = LEFT("FTPD",9)||LEFT("FTPD",9) 140 | sortin.x = user_group||"RANDOMPW N" 141 | sortin.0 = x 142 | call wto "FTPD Install: Sorting" 143 | CALL RXSORT 144 | call wto "FTPD Install: writting to disk" 145 | "EXECIO * DISKW USERS (STEM SORTIN. OPEN FINIS" 146 | end 147 | "FREE F(USERS)" 148 | say "USERS Closed" 149 | 150 | call wto "FTPD Install: Adding FTPAUTH facility class" 151 | 152 | 153 | p1 = "FACILITYSVC244 FTPD READ" 154 | p2 = "FACILITYFTPAUTH NONE" 155 | p3 = "FACILITYFTPAUTH ADMIN READ" 156 | 157 | 158 | "ALLOC FI(PROFILES) DA('SYS1.SECURE.CNTL(PROFILES)') SHR " 159 | "EXECIO * DISKR PROFILES (FINIS STEM sortin." 160 | 161 | if rc > 0 then do 162 | say "Error reading SYS1.SECURE.CNTL(PROFILES):" rc 163 | "FREE F(PROFILES)" 164 | exit 8 165 | end 166 | not_already_installed = 1 167 | do i = 1 to sortin.0 168 | if pos(p1, sortin.i) > 0 then not_already_installed = 0 169 | if pos(p2, sortin.i) > 0 then not_already_installed = 0 170 | if pos(p3, sortin.i) > 0 then not_already_installed = 0 171 | end 172 | 173 | if not_already_installed then do 174 | x = sortin.0 + 1; sortin.x = p1 175 | x = sortin.0 + 2; sortin.x = p2 176 | x = sortin.0 + 3; sortin.x = p3 177 | sortin.0 = x 178 | 179 | CALL RXSORT 180 | "EXECIO * DISKW PROFILES (STEM SORTIN. OPEN FINIS" 181 | end 182 | 183 | "FREE F(PROFILES)" 184 | call wto "FTPD Users and profiles installed" 185 | /* 186 | //SYSUT2 DD DSN=SYS2.EXEC(RAKFUPDT),DISP=SHR 187 | //* ********************************************************** 188 | //EXECSORT EXEC PGM=IKJEFT01,REGION=8192K 189 | //SYSTSIN DD * 190 | FREE FILE(RXLIB) 191 | ALLOC FILE(RXLIB) DSN('BREXX.CURRENT.RXLIB') SHR 192 | FREE FILE(SYSEXEC) 193 | ALLOC FILE(SYSEXEC) DSN('SYS2.EXEC') SHR 194 | RX SYS2.EXEC(RAKFUPDT) 195 | //SYSTSPRT DD SYSOUT=* 196 | //RAKFUSER EXEC RAKFUSER 197 | //RAKFPROF EXEC RAKFPROF -------------------------------------------------------------------------------- /source/build/objscan_input.nam: -------------------------------------------------------------------------------- 1 | FTPLOGIN rac_user_login 2 | FTPLGOUT rac_user_logout 3 | FTPAUTH rac_ftp_auth 4 | FTPSU rac_switch_user 5 | -------------------------------------------------------------------------------- /source/c/ftpd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MVS38j FTP server. By Jason Paul Winter, 2003. 3 | * 4 | * jasonwinter@hotmail.com, use "Subject" wisely - spam gets deleted without being read. 5 | * 6 | * Based on the "Dumb As Fudge FTP Daemon" written in Perl. 7 | * 8 | */ 9 | 10 | /* 11 | * RAC based authentication & authorization added by Juergen Winkelmann, 2015. 12 | * 13 | * winkelmann@id.ethz.ch 14 | * 15 | */ 16 | 17 | /* 18 | # Dumb As Fudge written in Perl: 19 | # 20 | # by James Ensor (ensor@xcf.berkeley.edu) 21 | # and Ian Turner (vectro@pipeline.com) 22 | # 23 | */ 24 | 25 | extern long __libc_arch; 26 | extern long __libc_oarch; 27 | extern long __libc_osvm; 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | 38 | /* Version information */ 39 | #define VERSION_MAJOR 1 40 | #define VERSION_MINOR 0 41 | #define VERSION_MICRO 1 42 | #define VERSION_SPECIAL "" 43 | 44 | 45 | #define STRINGIFY0(s) # s 46 | #define STRINGIFY(s) STRINGIFY0(s) 47 | 48 | #define VERSION STRINGIFY(VERSION_MAJOR)"."STRINGIFY(VERSION_MINOR)"."STRINGIFY(VERSION_MICRO)""VERSION_SPECIAL 49 | /* end version info */ 50 | 51 | #ifdef __DEBUG__ 52 | #define debug_output 1 53 | #endif 54 | /* 55 | char * strupr (char * s) { 56 | long i; 57 | 58 | i = 0; 59 | while (s [i] = toupper (s [i])) i++; 60 | 61 | return (s); 62 | }; 63 | */ 64 | 65 | #define MAXBUFLEN 4096 /* Normal buffer size for FTP - all/most NW cards/drivers support this. */ 66 | /* used for socket send/recv max size, buffers are twice as big (plus one for an optional terminator.) */ 67 | 68 | #define PATHLENGTH 260 69 | /* MAX Dataset name lenth with member and terminator */ 70 | #define MAXDSN 53 71 | 72 | #define DEFAULT_SRVIP "any" 73 | #define DEFAULT_SRVPORT 21 74 | #define DEFAULT_PASVADDR "127,0,0,1" 75 | 76 | #define FAKE_USER "MVSCE" 77 | #define FAKE_GROUP "MVSCE" 78 | 79 | #define DEFAULT_PARMLIB "SYS1.PARMLIB(FTPDPM00)" 80 | #define DSNFMT "//DSN:%s" 81 | 82 | /* TODO: Add custom banners */ 83 | #define WELCOME_MESSAGE "%s FTP server (MVS 3.8j) ready" 84 | 85 | #define INIT_MESSAGE "FTP000I Startup - FTPD Version %s - Starting with %d arguments." 86 | #define ARG_MESSAGES_S "FTP001I Startup - Argument (%d) %s = %s (replaces previous setting: %s)" 87 | #define ARG_MESSAGES_I "FTP001I Startup - Argument (%d) %s = %s (replaces previous setting: %d)" 88 | #define DDNAME_MESSAGES "FTP001I Startup - Argument (%d) DD Name %s added" 89 | #define PARMLIB_READ "FTP001I Startup - Parameter %s = '%s'" 90 | #define PARMLIB_MESSAGE "FTP002I Startup - Reading parmlib %s" 91 | #define DEVICE_MESSAGES "FTP003I Startup - Reading DASD %s,%s" 92 | #define DATASET_MESSAGE "FTP004I Startup - %d datasets" 93 | #define SOCKET_MESSAGE "FTP005I Startup Complete - FTP server listening on port: %u" 94 | #define CONNECT_MESSAGE "FTP00GI Connection from %d.%d.%d.%d - %.2d.%.2d.%.2d %d/%.2d/%.2d" 95 | #define SHUTDOWN_MESSAGE "FTP006I Shutdown complete" 96 | /* Error messages */ 97 | #define ERROR_MESSAGES "FTP001E Error - %s" 98 | #define PARLMIB_ERROR "FTP001E Error - Configuration file %s not available" 99 | #define PARLMIB_ERROR_L "FTP002E Warning - Line %d in %s not a valid option or disk" 100 | #define LISTEN_ERROR "FTP003E Error - Can't Listen on port %d (Errno: %d)" 101 | #define ARG_ERROR "FTP004E Warning - Argument (%d) %s = %s not recognized" 102 | #define PARLMIB_ERR_P "FTP005E Warning - Line %d in %s PASV start port is higher than end port: %d > %d. Using random port." 103 | #define PARLMIB_ERR_S "FTP005E Warning - Line %d in %s PASV passive port range too small. The minimum is 10. Using random port." 104 | #define PARLMIB_ERR_E "FTP005E Warning - Line %d in %s PASV passive port range must be > 1024. Using random port." 105 | #define PARLMIB_ERR_I "FTP005E Warning - Line %d in %s INSECURE must be 1 or 0" 106 | /* RAKF MESSAGES */ 107 | #define LOGIN_MESSAGE_C "FTP001R %s - logged in - TIME=%.2d.%.2d.%.2d DATE=%d/%.2d/%.2d" 108 | #define REJECT_MESSAGE "FTP002R %d.%d.%d.%d - rejected - %.2d.%.2d.%.2d %d/%.2d/%.2d" 109 | #define LOGOUT_MESSAGE_C "FTP003R %s - logged out - TIME=%.2d.%.2d.%.2d DATE=%d/%.2d/%.2d" 110 | #define LOGIN_MESSAGE "You are now logged in." 111 | #define LOGIN_FAILED_MESSAGE "Authentication failed, login rejected." 112 | /* Generic information */ 113 | #define GENERIC_MESSAGE "FTP00GI - %s" 114 | #define GENERIC_WARNING "FTP00GW - %s" 115 | #define GENERIC_ERROR "FTP00GE - %s" 116 | 117 | short SERVER_PORT; 118 | char SERVER_IP [16]; 119 | char *PARMLIB; 120 | char *PARMLIB_PRINT; 121 | char *AUTH_USER; 122 | short PASV_START_PORT; 123 | short PASV_END_PORT; 124 | int INSECURE; 125 | 126 | 127 | struct tm * td; 128 | time_t lt; 129 | char wtomsg [126]; 130 | char welcome [126]; 131 | 132 | extern unsigned int rac_user_login (char * user, char * pass); 133 | extern unsigned int rac_user_logout (unsigned int acee); 134 | extern unsigned int rac_switch_user (unsigned int acee); 135 | extern void rac_ftp_auth (unsigned int state); 136 | 137 | /**********************************************************************/ 138 | /**********************************************************************/ 139 | /**********************************************************************/ 140 | 141 | #include "mvsdirs.h" 142 | 143 | /**********************************************************************/ 144 | /**********************************************************************/ 145 | /**********************************************************************/ 146 | 147 | void user_logout (unsigned int acee, char * user) { 148 | rac_user_logout ((unsigned int) &acee); 149 | Sleep (1000); 150 | time (<); 151 | td = localtime (<); 152 | sprintf (wtomsg, LOGOUT_MESSAGE_C, user, (* td).tm_hour, (* td).tm_min, (* td).tm_sec, (* td).tm_year + 1900, (* td).tm_mon + 1, (* td).tm_mday); 153 | _write2op (wtomsg); 154 | return; 155 | } 156 | 157 | /**** The following is a back-up mechanism for OSs like Windows that don't return information in getsockname. */ 158 | char PASVADDR[16]; /* PASV style string for hosts that don't return their IP Address. */ 159 | 160 | static long running = 1; /* We are running */ 161 | static long optimise = 0; /* The largest numbered socket descriptor, for 'select' processing */ 162 | 163 | static fd_set rbits; 164 | static fd_set wbits; 165 | 166 | typedef struct cb_data_tag * cb_data_ptr; 167 | typedef struct cb_data_tag { 168 | FILE * fh; /* Will be *one* of FILE or SOCKET */ 169 | long rcallback; /* Files are always non-blocking, */ 170 | long wcallback; /* so just signal if an operation is needed. */ 171 | SOCKET FH; /* Isn't FILE... */ 172 | void * FUNC_DATA; /* Data pointer to the struct below, or NULL */ 173 | void* (*READ_FUNC) (void *, void *); /* function address to call for reads */ 174 | void* (*WRITE_FUNC) (void *, void *); /* function address to call for writes */ 175 | cb_data_ptr next; /* This is a list structure. */ 176 | } cb_data_t; 177 | 178 | static cb_data_ptr head = NULL; /* Nothing in the list yet. */ 179 | 180 | typedef struct data_tag * data_tag_ptr; 181 | typedef struct data_tag { 182 | long use_count; /* May have many references, so only delete the structure when no more exist. */ 183 | SOCKET SOCK; /* The MASTER-SOCKET which this data belongs to */ 184 | long DONE; /* Signal that the MASTER-SOCKET will be used no more. */ 185 | char * READ_BUF; /* Incomming commands buffer */ 186 | long READ_BUF_LENGTH; 187 | char * WRITE_BUF; /* outgoing responses buffer */ 188 | long WRITE_BUF_LENGTH; 189 | unsigned int ACEE; /* access control environment element address */ 190 | char TYPE; /* A-Ascii, I-Binary */ 191 | char STRU; /* F-No structure */ 192 | char MODE; /* S-Stream */ 193 | char USER[9]; /* user from last USER command entered */ 194 | struct sockaddr * DATA_CONNECT_ADDR; /* malloc'd area for building connect to client-socket information */ 195 | SOCKET DATA_SOCK; /* All file related data transfers uses this SOCKET. */ 196 | SOCKET DATA_LISTEN_SOCK; /* PASV mode transfers allow connecting to this SOCKET. */ 197 | FILE * FILE; /* File transfers read/write to this descriptor. */ 198 | char DATA_SOCK_STATUS; /* A-Active, C-Connected, F-Failed, L-Listening */ 199 | long DATA_SOCK_FAILURE_REASON; /* Will be errno */ 200 | char XFER; /* File data direction R/W or not set (0) */ 201 | char * DATA_SOCK_BUF; /* input / output data for file transfers */ 202 | long DATA_SOCK_BUF_LEN; 203 | char * CWD; /* This users current working directory (ie. root:'\' or PDS:'\...\' in MVS) */ 204 | } data_t; 205 | 206 | /* 207 | # ftp_servlets have: 208 | # control - control connection 209 | # data - data connection 210 | # auth - true or false 211 | # pasv - true or false 212 | # conn_addr - a sockaddr_in to connect to (given by the PORT command) or NULL 213 | 214 | # select loop: 215 | # a list 216 | # keys are file descriptors (numbers) 217 | # values are (refs to) : filehandle, data, read_func, write_func (could add accept_func) 218 | # functions are called like this: 219 | # read_func(filehandle, data) 220 | # read_func or write_func can be NULL (won't be selected on) 221 | */ 222 | 223 | static long init () { 224 | FD_ZERO (&rbits); /* Ensure no SOCKETs are set in the 'select' buffers */ 225 | FD_ZERO (&wbits); 226 | 227 | return (0); 228 | }; 229 | 230 | static void callbacks_delete_f (FILE * fd) { 231 | data_tag_ptr d; 232 | cb_data_ptr p; 233 | cb_data_ptr t; 234 | 235 | p = NULL; 236 | t = head; 237 | while (t) { 238 | if (t->fh == fd) { 239 | 240 | #ifdef debug_output 241 | printf ("Removing File CallBack: %d\n", (int)fd); 242 | #endif 243 | 244 | if (p) { 245 | p->next = t->next; 246 | } else { 247 | head = t->next; 248 | }; 249 | if (t->FUNC_DATA) { 250 | d = t->FUNC_DATA; 251 | d->use_count--; 252 | if (d->use_count == 0) { 253 | 254 | if (d->READ_BUF) free (d->READ_BUF); 255 | if (d->WRITE_BUF) free (d->WRITE_BUF); 256 | if (d->DATA_CONNECT_ADDR) free (d->DATA_CONNECT_ADDR); 257 | if (d->DATA_SOCK_BUF) free (d->DATA_SOCK_BUF); 258 | if (d->CWD) free (d->CWD); 259 | if (d->ACEE > 1) user_logout (d->ACEE, d->USER); 260 | 261 | free (d); 262 | }; 263 | }; 264 | free (t); 265 | return; 266 | }; 267 | p = t; 268 | t = t->next; 269 | }; 270 | }; 271 | 272 | static void callbacks_delete (SOCKET fd) { 273 | data_tag_ptr d; 274 | cb_data_ptr p; 275 | cb_data_ptr t; 276 | 277 | p = NULL; 278 | t = head; 279 | while (t) { 280 | if (t->FH == fd) { 281 | 282 | #ifdef debug_output 283 | printf ("Removing Socket CallBack: %d\n", fd); 284 | #endif 285 | 286 | if (p) { 287 | p->next = t->next; 288 | } else { 289 | head = t->next; 290 | }; 291 | if (t->FUNC_DATA) { 292 | d = t->FUNC_DATA; 293 | d->use_count--; 294 | if (d->use_count == 0) { 295 | 296 | if (d->READ_BUF) free (d->READ_BUF); 297 | if (d->WRITE_BUF) free (d->WRITE_BUF); 298 | if (d->DATA_CONNECT_ADDR) free (d->DATA_CONNECT_ADDR); 299 | if (d->DATA_SOCK_BUF) free (d->DATA_SOCK_BUF); 300 | if (d->CWD) free (d->CWD); 301 | if (d->ACEE > 1) user_logout (d->ACEE, d->USER); 302 | 303 | free (d); 304 | }; 305 | }; 306 | free (t); 307 | return; 308 | }; 309 | p = t; 310 | t = t->next; 311 | }; 312 | }; 313 | 314 | static long vec (fd_set * bits, SOCKET fd, long value) { /* value = -1 just returns the current status */ 315 | 316 | if (value != -1) { 317 | 318 | if (value != 0) { 319 | 320 | #ifdef debug_output 321 | if (&rbits == bits) { 322 | printf ("Setting Read Socket %d on\n", fd); 323 | } else { 324 | printf ("Setting Write Socket %d on\n", fd); 325 | }; 326 | #endif 327 | 328 | FD_SET (fd, bits); 329 | 330 | return (1); 331 | 332 | } else { 333 | 334 | #ifdef debug_output 335 | if (&rbits == bits) { 336 | printf ("Setting Read Socket %d off\n", fd); 337 | } else { 338 | printf ("Setting Write Socket %d off\n", fd); 339 | }; 340 | #endif 341 | 342 | FD_CLR (fd, bits); 343 | 344 | return (0); 345 | }; 346 | }; 347 | 348 | return (FD_ISSET (fd, bits)); 349 | }; 350 | 351 | /* 352 | ### callback functions 353 | */ 354 | 355 | /* 356 | # add a callbacks to the list, or modify existing callbacks 357 | # takes: 358 | # filehandle 359 | # data for callbacks 360 | # function to call when readable 361 | # function to call when writeable 362 | */ 363 | static void set_callbacks_f (FILE * fh, void * func_data, void * read_func, void * write_func) { 364 | data_tag_ptr d; 365 | cb_data_ptr cb_data; 366 | cb_data_ptr s; 367 | 368 | if (fh == NULL) { 369 | printf ("no file descriptor for fh, can't set callbacks\n"); 370 | running = 0; 371 | return; 372 | } 373 | 374 | cb_data = (cb_data_ptr)malloc (sizeof (cb_data_t)); 375 | 376 | // register the callback data 377 | cb_data->fh = fh; 378 | cb_data->rcallback = (read_func != NULL); 379 | cb_data->wcallback = (write_func != NULL); 380 | 381 | #ifdef debug_output 382 | if (cb_data->rcallback) { 383 | printf ("Setting Read File %d on\n", fh); 384 | } else { 385 | printf ("Setting Read File %d off\n", fh); 386 | }; 387 | 388 | if (cb_data->wcallback) { 389 | printf ("Setting Write File %d on\n", fh); 390 | } else { 391 | printf ("Setting Write File %d off\n", fh); 392 | }; 393 | #endif 394 | 395 | cb_data->FH = -1; 396 | 397 | cb_data->FUNC_DATA = func_data; 398 | if (func_data != NULL) { 399 | d = func_data; 400 | d->use_count++; 401 | }; 402 | 403 | cb_data->READ_FUNC = read_func; 404 | cb_data->WRITE_FUNC = write_func; 405 | 406 | if (head != NULL) { 407 | cb_data->next = NULL; 408 | s = head; 409 | while (s->next != NULL) s = s->next; 410 | s->next = cb_data; 411 | } else { 412 | cb_data->next = head; 413 | head = cb_data; 414 | }; 415 | }; 416 | 417 | static void set_callbacks (SOCKET fh, void * func_data, void * read_func, void * write_func) { 418 | data_tag_ptr d; 419 | cb_data_ptr cb_data; 420 | cb_data_ptr s; 421 | 422 | if (fh < 0) { 423 | printf ("no file descriptor for fh, can't set callbacks\n"); 424 | running = 0; 425 | return; 426 | } 427 | 428 | cb_data = (cb_data_ptr)malloc (sizeof (cb_data_t)); 429 | 430 | // register the callback data 431 | cb_data->fh = NULL; 432 | cb_data->rcallback = 0; 433 | cb_data->wcallback = 0; 434 | 435 | cb_data->FH = fh; 436 | 437 | cb_data->FUNC_DATA = func_data; 438 | if (func_data != NULL) { 439 | d = func_data; 440 | d->use_count++; 441 | }; 442 | 443 | cb_data->READ_FUNC = read_func; 444 | cb_data->WRITE_FUNC = write_func; 445 | 446 | if (head != NULL) { 447 | cb_data->next = NULL; 448 | s = head; 449 | while (s->next != NULL) s = s->next; 450 | s->next = cb_data; 451 | } else { 452 | cb_data->next = head; 453 | head = cb_data; 454 | }; 455 | 456 | // turn on/off the appropriate callback bits 457 | vec (&rbits, fh, (long)read_func); /* Set on only if read_func is set */ 458 | vec (&wbits, fh, (long)write_func); /* Set on only if write_func is set */ 459 | }; 460 | 461 | /* 462 | # disable the read callback for the given filehandle 463 | # this can be reversed by enable_read_callback() 464 | # read callbacks are automatically enabled when added/specified by set_callbacks() 465 | # (takes: a filehandle) 466 | */ 467 | 468 | static void disable_read_callback_f (FILE * fh) { 469 | cb_data_ptr s; 470 | 471 | s = head; 472 | while (s) { 473 | if (s->fh == fh) { 474 | #ifdef debug_output 475 | printf ("Setting Read File %d off\n", fh); 476 | #endif 477 | s->rcallback = 0; 478 | break; 479 | }; 480 | s = s->next; 481 | }; 482 | }; 483 | 484 | static void disable_read_callback (SOCKET fh) { 485 | 486 | vec (&rbits, fh, 0); 487 | }; 488 | 489 | /* 490 | # enables a read callback disabled by disable_read_callback() 491 | # (takes: a filehandle) 492 | */ 493 | static void enable_read_callback_f (FILE * fh) { 494 | cb_data_ptr s; 495 | 496 | s = head; 497 | while (s) { 498 | if (s->fh == fh) { 499 | #ifdef debug_output 500 | printf ("Setting Read File %d on\n", fh); 501 | #endif 502 | s->rcallback = 1; 503 | break; 504 | }; 505 | s = s->next; 506 | }; 507 | }; 508 | 509 | static void enable_read_callback (SOCKET fh) { 510 | 511 | vec (&rbits, fh, 1); 512 | }; 513 | 514 | /* 515 | # disable the write callback for the given filehandle 516 | # this can be reversed by enable_write_callback() 517 | # write callbacks are automatically enabled when added/specified by set_callbacks() 518 | # (takes: a filehandle) 519 | */ 520 | static void disable_write_callback_f (FILE * fh) { 521 | cb_data_ptr s; 522 | 523 | s = head; 524 | while (s) { 525 | if (s->fh == fh) { 526 | #ifdef debug_output 527 | printf ("Setting Write File %d off\n", fh); 528 | #endif 529 | s->wcallback = 0; 530 | break; 531 | }; 532 | s = s->next; 533 | }; 534 | }; 535 | 536 | static void disable_write_callback (SOCKET fh) { 537 | 538 | vec (&wbits, fh, 0); 539 | }; 540 | 541 | /* 542 | # enables a write callback disabled by disable_write_callback() 543 | # (takes: a filehandle) 544 | */ 545 | static void enable_write_callback_f (FILE * fh) { 546 | cb_data_ptr s; 547 | 548 | s = head; 549 | while (s) { 550 | if (s->fh == fh) { 551 | #ifdef debug_output 552 | printf ("Setting Write File %d on\n", fh); 553 | #endif 554 | s->wcallback = 1; 555 | break; 556 | }; 557 | s = s->next; 558 | }; 559 | }; 560 | 561 | static void enable_write_callback (SOCKET fh) { 562 | 563 | vec (&wbits, fh, 1); 564 | }; 565 | 566 | /* 567 | # remove a callback from this list (generally, this will automatically close 568 | # the associated file handle) 569 | # (takes a filehandle) 570 | */ 571 | static void remove_callbacks_f (FILE * fh) { 572 | 573 | if (fh == NULL) return; 574 | 575 | #ifdef debug_output 576 | printf ("Setting Read File %d off\n", fh); 577 | printf ("Setting Write File %d off\n", fh); 578 | #endif 579 | 580 | callbacks_delete_f (fh); 581 | }; 582 | 583 | static void remove_callbacks (SOCKET fh) { 584 | 585 | if (fh < 0) return; 586 | 587 | // turn off the appropriate callback bits 588 | vec (&rbits, fh, 0); 589 | vec (&wbits, fh, 0); 590 | 591 | callbacks_delete (fh); 592 | }; 593 | 594 | /* 595 | # call select() and run the appropriate callbacks 596 | */ 597 | static void run_callbacks () { 598 | fd_set rout; 599 | fd_set wout; 600 | cb_data_ptr cb_data; 601 | cb_data_ptr cb_next; 602 | SOCKET fd; 603 | FILE * fh; 604 | long i; 605 | long j; 606 | long * s; 607 | timeval t; 608 | 609 | t.sec = 2; // Wake every 2 seconds 610 | t.usec = 0; // plus 0 microsecs. 611 | 612 | while (running) { 613 | 614 | cb_data = head; 615 | while (cb_data) { /* First, check file-processing to-do: */ 616 | 617 | fh = cb_data->fh; 618 | cb_next = cb_data->next; /* In case it's deleted below... */ 619 | 620 | if (fh != NULL) { /* Files only */ 621 | 622 | /* if this fh is ready for reading/writing and the callback exists */ 623 | if ((cb_data->rcallback) && (cb_data->READ_FUNC)) { 624 | 625 | (*cb_data->READ_FUNC) ((void *)fh, cb_data->FUNC_DATA); /* call it */ 626 | 627 | } else /* Can't have both readers and writers */ 628 | if ((cb_data->wcallback) && (cb_data->WRITE_FUNC)) { 629 | 630 | (*cb_data->WRITE_FUNC) ((void *)fh, cb_data->FUNC_DATA); /* call it */ 631 | }; 632 | }; 633 | 634 | cb_data = cb_next; 635 | }; 636 | 637 | 638 | rout = rbits; /* Never modify the real ones... */ 639 | wout = wbits; /* 'select' always returns a new array. */ 640 | 641 | /* Calculate the MAX.SOCKET number in: i */ 642 | i = ((optimise + 31) / 32) - 1; /* Get word ptr to last 'long' */ 643 | while ((i > 0) && 644 | (((long *)&rout) [i] == 0) && 645 | (((long *)&wout) [i] == 0)) i--; 646 | 647 | i = ((i + 1) * 32) - 1; /* Highest Socket to check */ 648 | if (i > optimise) i = optimise; /* may be greater than the known value */ 649 | 650 | while ((i >= 0) && 651 | (FD_ISSET (i, &rout) == 0) && 652 | (FD_ISSET (i, &wout) == 0)) i--; 653 | 654 | if (i < 0) i = 0; // Algorithm fix for IBM sockets (descr 0 allowed) 655 | 656 | #ifdef debug_output 657 | printf ("select entered c=%d r=%08X w=%08X\n", i + 1, ((long *)&rout) [0], ((long *)&wout) [0]); 658 | #endif 659 | 660 | if (__libc_osvm == 0) { // z/OS/390 or MVS3.8j? No - Don't scan CIB chain 661 | 662 | i = select (i + 1, &rout, &wout, NULL, NULL); /* Wait - No timelimit. */ 663 | 664 | } else { 665 | 666 | j = 0; 667 | while ((j == 0) && (running)) { 668 | 669 | j = select (i + 1, &rout, &wout, NULL, &t); /* Wait - With a timelimit. */ 670 | 671 | if (j == 0) { // Scan the CIB chain for a STOP command 672 | s = (*((long **)548)); // 548->ASCB 673 | s = ((long **)s) [14]; // 56->CSCB 674 | if (s) { 675 | if (__libc_oarch) { // z/OS/390 uses an extension 676 | s = ((long **)s) [55]; // 220->CSCX 677 | if (s) // ...Maybe. 678 | s = ((long **)s) [3];// 12->CIB 679 | } else 680 | s = ((long **)s) [11]; // 44->CIB 681 | 682 | while (s) { 683 | if (((unsigned char *)s) [4] == 0x40) { 684 | running = 0; // Found a STOP command queued 685 | break; 686 | } 687 | s = ((long **)s) [0];// 0->NEXT-CIB 688 | } 689 | } 690 | if (running) { // no STOP command found, reinitialize arrays 691 | rout = rbits; /* Never modify the real ones... */ 692 | wout = wbits; /* 'select' always returns a new array. */ 693 | } 694 | } 695 | } 696 | i = j; 697 | } 698 | 699 | #ifdef debug_output 700 | printf ("select woke up s=%d r=%08X w=%08X errno=%d\n", i, ((long *)&rout) [0], ((long *)&wout) [0], errno); 701 | #endif 702 | 703 | /* 704 | # need to check if callback exists before running it 705 | # because one callback can remove another 706 | */ 707 | 708 | cb_data = head; 709 | while (cb_data) { 710 | 711 | fd = cb_data->FH; 712 | cb_next = cb_data->next; /* In case it's deleted below... */ 713 | 714 | if (fd != -1) { /* Sockets only */ 715 | 716 | /* if this fd is ready for reading/writing and the callback exists */ 717 | if ((vec (&rout, fd, -1)) && (cb_data->READ_FUNC)) { 718 | 719 | (*cb_data->READ_FUNC) ((void *)fd, cb_data->FUNC_DATA); /* call it */ 720 | 721 | } else /* Can't have both readers and writers */ 722 | if ((vec(&wout, fd, -1)) && (cb_data->WRITE_FUNC)) { 723 | 724 | (*cb_data->WRITE_FUNC) ((void *)fd, cb_data->FUNC_DATA); /* call it */ 725 | }; 726 | }; 727 | 728 | cb_data = cb_next; 729 | }; 730 | }; 731 | }; 732 | 733 | /* 734 | ### general utility functions 735 | */ 736 | 737 | /* 738 | # reads a line (up to LF) from the beginning of a string, and returns it 739 | # the line is removed from the string (returns NULL for a line without a linefeed.) 740 | */ 741 | static char * grab_line (char * in, long * len) { 742 | long i; 743 | long j; 744 | char * a = NULL; 745 | 746 | if (*len == 0) return (NULL); 747 | 748 | i = 0; 749 | while (i < *len) 750 | if (in [i++] == '\n') break; 751 | 752 | if (i < *len) { 753 | a = (char *)malloc (i + 1); 754 | memcpy (a, in, i); 755 | a [i] = 0; 756 | j = i; 757 | 758 | if (a [--j] == '\n') a [i] = 0; 759 | if (a [--j] == '\r') a [i] = 0; 760 | 761 | *len -= i; 762 | memcpy (in, &(in [i]), *len); 763 | 764 | } else if (in [i - 1] == '\n') { 765 | 766 | a = (char *)malloc (i + 1); 767 | memcpy (a, in, i); 768 | a [i] = 0; 769 | 770 | if (a [--i] == '\n') a [i] = 0; 771 | if (a [--i] == '\r') a [i] = 0; 772 | 773 | in [0] = 0; 774 | *len = 0; 775 | }; 776 | 777 | return (a); 778 | }; 779 | 780 | /* 781 | ### general I/O functions 782 | */ 783 | 784 | /* 785 | # read some data onto the end of a string using sysread() 786 | # (takes: filehandle, buffer, optional number of bytes to read) 787 | # (returns number of bytes read, like sysread()) 788 | */ 789 | static long read_into_buf_f (FILE * fh, char * buffer) { 790 | long i; 791 | 792 | i = fread (buffer, 1, MAXBUFLEN, fh); 793 | 794 | #ifdef debug_output 795 | /* 796 | buffer [i] = 0; 797 | printf ("Read %d, %d bytes: %s\n", (int)fh, i, buffer); 798 | */ 799 | printf ("Read %d, %d bytes.\n", (int)fh, i); 800 | #endif 801 | 802 | return (i); 803 | }; 804 | 805 | static long read_into_buf (SOCKET fh, char * buffer) { 806 | long i; 807 | 808 | i = recv (fh, buffer, MAXBUFLEN, 0); 809 | 810 | #ifdef debug_output 811 | printf ("Recv %d, %d bytes.\n", (int)fh, i); 812 | #endif 813 | 814 | return (i); 815 | }; 816 | 817 | /* 818 | # write as much data as possible from the beginning of a string 819 | # all data written will be removed from the string 820 | # (takes: filehandle, buffer) 821 | # (returns number of bytes written, like syswrite()) 822 | */ 823 | static long write_from_buf_f (FILE * fh, char * buffer, long size) { 824 | long bytes_written; 825 | 826 | bytes_written = fwrite (buffer, 1, size, fh); 827 | 828 | #ifdef debug_output 829 | /* 830 | buffer [size] = 0; 831 | printf ("Write %d, %d bytes (of %d bytes:) %s\n", (int)fh, bytes_written, size, buffer); 832 | */ 833 | printf ("Write %d, %d bytes (of %d bytes.)\n", (int)fh, bytes_written, size); 834 | #endif 835 | 836 | return (bytes_written); 837 | }; 838 | 839 | static long write_from_buf (SOCKET fh, char * buffer, long size) { 840 | long bytes_written; 841 | long newsize; 842 | 843 | if (size > MAXBUFLEN) { 844 | newsize = MAXBUFLEN; 845 | } else { 846 | newsize = size; 847 | }; 848 | 849 | bytes_written = send (fh, buffer, newsize, 0); 850 | 851 | #ifdef debug_output 852 | printf ("Send %d, %d bytes (of %d bytes.)\n", (int)fh, bytes_written, size); 853 | #endif 854 | 855 | if ((bytes_written > 0) && (size - bytes_written > 0)) { 856 | memcpy (buffer, &(buffer [bytes_written]), size - bytes_written); 857 | }; 858 | 859 | return (bytes_written); 860 | }; 861 | 862 | /* 863 | ### socket utility functions 864 | 865 | # make a socket that listens on the given port 866 | # (takes a port number, and an optional ip address (packed)) 867 | # (returns a reference to a socket) 868 | */ 869 | static SOCKET listen_sock (short port, long ip) { 870 | SOCKET LISTEN_SOCK; 871 | SOCKADDR_IN Slocal_adx; 872 | 873 | LISTEN_SOCK = socket (PF_INET, SOCK_STREAM, 0); 874 | 875 | if (LISTEN_SOCK != -1) { 876 | 877 | Slocal_adx.sin_family = AF_INET; 878 | Slocal_adx.sin_addr.s_addr = ip; 879 | Slocal_adx.sin_port = htons (port); 880 | 881 | if (bind (LISTEN_SOCK, (LPSOCKADDR)&Slocal_adx, sizeof (Slocal_adx)) != -1) { 882 | 883 | if (listen (LISTEN_SOCK, SOMAXCONN) == -1) { 884 | closesocket (LISTEN_SOCK); 885 | return (-1); 886 | }; 887 | } else { 888 | closesocket (LISTEN_SOCK); 889 | return (-1); 890 | }; 891 | } else { 892 | return (-1); 893 | }; 894 | 895 | if (optimise < LISTEN_SOCK) optimise = LISTEN_SOCK; 896 | 897 | return (LISTEN_SOCK); 898 | }; 899 | 900 | /* 901 | # accept a connection from the given socket 902 | # (takes a reference to a filehandle, like one from listen_sock()) 903 | # (returns a reference to a filehandle) 904 | */ 905 | static SOCKET accept_sock (SOCKET listen_sock) { 906 | SOCKET ACCEPT_SOCK; 907 | 908 | ACCEPT_SOCK = accept (listen_sock, NULL, NULL); 909 | 910 | if (ACCEPT_SOCK == -1) return (-1); 911 | 912 | if (optimise < ACCEPT_SOCK) optimise = ACCEPT_SOCK; 913 | 914 | return (ACCEPT_SOCK); 915 | }; 916 | 917 | /* 918 | # makes a non-blocking socket 919 | # (takes no args) 920 | */ 921 | static SOCKET nb_sock () { 922 | SOCKET NB_SOCK; 923 | long i = 1; 924 | 925 | NB_SOCK = socket (PF_INET, SOCK_STREAM, 0); 926 | 927 | if (NB_SOCK != -1) { 928 | 929 | if (optimise < NB_SOCK) optimise = NB_SOCK; 930 | 931 | ioctlsocket (NB_SOCK, FIONBIO, &i); 932 | return (NB_SOCK); 933 | }; 934 | 935 | return (-1); 936 | }; 937 | 938 | /* 939 | # makes a non-blocking socket 940 | # and tries to connect it to the given address 941 | # (takes: a sockaddr to connect to) 942 | */ 943 | static SOCKET nb_connect_sock (struct sockaddr * addr, long * i) { 944 | SOCKET SOCK; 945 | 946 | SOCK = nb_sock(); 947 | 948 | if (SOCK != -1) 949 | if (connect (SOCK, addr, sizeof (struct sockaddr)) == -1) { 950 | 951 | *i = HercGetLastError (); 952 | if ((*i != WSAEWOULDBLOCK) && 953 | (*i != WSAEINPROGRESS)) { 954 | 955 | closesocket (SOCK); 956 | return (-1); 957 | }; 958 | }; 959 | 960 | return (SOCK); 961 | }; 962 | 963 | /* 964 | # gets the IP that a socket is bound to 965 | # (takes a ref to a fh) 966 | */ 967 | static unsigned long sock_ip (SOCKET SOCK) { 968 | struct sockaddr a; 969 | struct sockaddr_in * b; 970 | int i = sizeof (a); 971 | 972 | b = (void *)&a; 973 | 974 | getsockname (SOCK, &a, &i); 975 | 976 | return (b->sin_addr.s_addr); 977 | }; 978 | 979 | /* 980 | ### generic callback 981 | */ 982 | 983 | /* 984 | # write data to the buffer for the given filehandle, and wake up the callback for it 985 | # (takes: a filehandle, a buffer, and data to write) 986 | */ 987 | static void generic_write (SOCKET fh, char * buff, char * data) { 988 | 989 | strcpy (buff, data); 990 | 991 | enable_write_callback (fh); 992 | }; 993 | 994 | /* 995 | # write a line to the buffer for a given filehandle, and wake up the callback for it 996 | # (takes: a filehandle, a buffer, and data to write) 997 | */ 998 | static void generic_write_line (SOCKET fh, char * buff, char * data) { 999 | 1000 | strcpy (buff, data); 1001 | strcat (buff, "\r\n"); 1002 | 1003 | enable_write_callback (fh); 1004 | }; 1005 | 1006 | /* 1007 | # checks if the given socket is connected, by trying to reconnect 1008 | */ 1009 | static long check_sock_connect (SOCKET fh, struct sockaddr * addr) { 1010 | long i; 1011 | 1012 | if (fh == -1) { 1013 | printf ("check_sock_connect: fh is undefined\n"); 1014 | return (0); 1015 | }; 1016 | 1017 | if (addr == NULL) { 1018 | printf ("check_sock_connect: addr is undefined\n"); 1019 | return (0); 1020 | }; 1021 | 1022 | i = connect (fh, addr, sizeof (struct sockaddr)); 1023 | 1024 | if (i == 0) return (1); 1025 | 1026 | i = HercGetLastError (); 1027 | 1028 | switch (i) { 1029 | case WSAEISCONN : 1030 | case WSAEINPROGRESS : 1031 | case WSAEALREADY : 1032 | case WSAEINVAL : /* Added for windows */ 1033 | return (1); 1034 | default : 1035 | return (0); 1036 | }; 1037 | }; 1038 | 1039 | /* 1040 | ### ftp server stuff 1041 | */ 1042 | 1043 | /* 1044 | # write a line out the control connection, and enable its callback 1045 | */ 1046 | static void ctl_write_line (data_tag_ptr data, char * line) { 1047 | 1048 | generic_write_line (data->SOCK, &(data->WRITE_BUF [data->WRITE_BUF_LENGTH]), line); 1049 | data->WRITE_BUF_LENGTH = strlen (data->WRITE_BUF); 1050 | }; 1051 | 1052 | /* 1053 | # takes a sockaddr and converts it to a comma-separated list of 6 numbers 1054 | */ 1055 | static char * sockaddr_to_comma_list (SOCKET SOCK) { 1056 | struct sockaddr a; 1057 | struct sockaddr_in * b; 1058 | struct herc_in_addr l; 1059 | int i = sizeof (a); 1060 | char * s; 1061 | unsigned char c; 1062 | unsigned char d; 1063 | unsigned short p; 1064 | 1065 | b = (void *)&a; 1066 | 1067 | getsockname (SOCK, &a, &i); 1068 | 1069 | s = (char *)malloc (30); 1070 | 1071 | p = ntohs (b->sin_port); 1072 | d = p % 256; 1073 | c = (p - d) / 256; 1074 | 1075 | /* *** NOT ALL IMPLEMENTATIONS EXPORT THE HOST IP-ADDRESS, SO USE PASVADDR *** */ 1076 | /* if (b->sin_addr.s_addr) { 1077 | l.S_un.S_addr = b->sin_addr.s_addr; 1078 | sprintf (s, "%d,%d,%d,%d,%d,%d", l.S_un.S_un_b.s_b1, l.S_un.S_un_b.s_b2, l.S_un.S_un_b.s_b3, l.S_un.S_un_b.s_b4, c, d); 1079 | } else { */ 1080 | sprintf (s, "%s,%d,%d", PASVADDR, c, d); 1081 | /* }; */ 1082 | return (s); 1083 | }; 1084 | 1085 | /* 1086 | # takes a comma-separated list of 6 numbers (or something containing it) 1087 | # and converts it to a sockaddr 1088 | */ 1089 | static long comma_list_to_sockaddr (struct sockaddr_in * b, char * str) { 1090 | long i; 1091 | long j; 1092 | long k; 1093 | char tstr [30]; 1094 | unsigned short p; 1095 | struct herc_in_addr l; 1096 | long t; 1097 | 1098 | strcpy (tstr, str); 1099 | 1100 | b->sin_family = AF_INET; 1101 | 1102 | i = 0; 1103 | j = 0; 1104 | k = 0; 1105 | while (1) { 1106 | if ((tstr [i] == ',') || (tstr [i] == 0)) { 1107 | tstr [i] = 0; 1108 | t = atol (&(tstr [j])); 1109 | switch (k) { 1110 | case 0: 1111 | l.S_un.S_un_b.s_b1 = (unsigned char)t; 1112 | break; 1113 | case 1: 1114 | l.S_un.S_un_b.s_b2 = (unsigned char)t; 1115 | break; 1116 | case 2: 1117 | l.S_un.S_un_b.s_b3 = (unsigned char)t; 1118 | break; 1119 | case 3: 1120 | l.S_un.S_un_b.s_b4 = (unsigned char)t; 1121 | b->sin_addr.s_addr = l.S_un.S_addr; 1122 | break; 1123 | case 4: 1124 | p = t * 256; 1125 | break; 1126 | case 5: 1127 | p += (short)t; 1128 | b->sin_port = htons (p); 1129 | }; 1130 | 1131 | k++; 1132 | if (str [i] == 0) break; 1133 | 1134 | i++; 1135 | j = i; 1136 | } else { 1137 | i++; 1138 | }; 1139 | }; 1140 | 1141 | if (k == 6) { 1142 | return 1; 1143 | } else { 1144 | return 0; 1145 | }; 1146 | }; 1147 | 1148 | /* 1149 | # takes an server response code (like 230) 1150 | # and an ftp message (which may be several lines, and may not be empty) 1151 | # and returns it as the server should print it 1152 | # for example: (this would be a single output:) 1153 | # 230-A 1154 | # 230-Multi-Line 1155 | # 230 Welcome Message 1156 | */ 1157 | static char * ftp_server_msg (long code, char * message) { 1158 | long i; 1159 | long l; 1160 | char * a; 1161 | char * b; 1162 | char * result; 1163 | char tstr [16]; 1164 | 1165 | if ((message) && (message [0])) { 1166 | i = 0; 1167 | a = message; 1168 | do { 1169 | i++; 1170 | a = strstr (&(a [1]), "\n"); 1171 | } while (a); 1172 | 1173 | i = strlen (message) + (10 * i); 1174 | 1175 | result = (char *)malloc (i); 1176 | result [0] = 0; 1177 | 1178 | sprintf (tstr, "%d-", code); 1179 | l = strlen (tstr); 1180 | 1181 | i = 0; 1182 | a = message; 1183 | do { 1184 | b = strstr (&(a [1]), "\n"); 1185 | 1186 | if (b) { 1187 | strncpy (&(result [i]), tstr, i); 1188 | i += l; 1189 | strncpy (&(result [i]), a, (a - b)); 1190 | i += (a - b); 1191 | } else { 1192 | sprintf (tstr, "%d ", code); 1193 | strncpy (&(result [i]), tstr, l); 1194 | i += l; 1195 | strcpy (&(result [i]), a); 1196 | strcat (&(result [i]), "\r\n"); 1197 | }; 1198 | 1199 | a = b; 1200 | } while (a); 1201 | 1202 | } else { 1203 | 1204 | result = (char *)malloc (16); 1205 | sprintf (result, "%d \r\n", code); 1206 | }; 1207 | 1208 | return (result); 1209 | }; 1210 | 1211 | /* 1212 | # (takes a socket representing the control connection) 1213 | */ 1214 | static data_tag_ptr new_ftp_data (SOCKET SOCK) { 1215 | char * s; 1216 | char sysname[4]; 1217 | //char *sysname = _sysname(); 1218 | 1219 | data_tag_ptr data; 1220 | 1221 | data = malloc (sizeof (data_t)); 1222 | 1223 | data->use_count = 0; 1224 | 1225 | data->SOCK = SOCK; 1226 | 1227 | data->DONE = 0; 1228 | 1229 | data->READ_BUF = (char *)malloc ((MAXBUFLEN * 2) + 1); 1230 | data->READ_BUF [0] = 0; 1231 | data->READ_BUF_LENGTH = 0; 1232 | data->WRITE_BUF = (char *)malloc ((MAXBUFLEN * 2) + 1); 1233 | 1234 | _sysname(sysname); 1235 | sprintf (welcome, WELCOME_MESSAGE, sysname); 1236 | 1237 | s = ftp_server_msg (220, welcome); 1238 | strcpy (data->WRITE_BUF, s); 1239 | free (s); 1240 | data->WRITE_BUF_LENGTH = strlen (data->WRITE_BUF); 1241 | 1242 | data->ACEE = 0; 1243 | data->TYPE = 'A'; /* either "I" (binary) or "A" (ascii) */ 1244 | /* STRU is always "F" (no record structure) */ 1245 | data->STRU = 'F'; 1246 | /* MODE is always "S" (stream) */ 1247 | data->MODE = 'S'; 1248 | 1249 | /* ### data sock stuff */ 1250 | 1251 | data->DATA_CONNECT_ADDR = NULL; 1252 | data->DATA_SOCK = -1; /* the socket used to transfer data */ 1253 | data->DATA_LISTEN_SOCK = -1; /* the socket to wait for a connection on, in PASV mode */ 1254 | data->FILE = NULL; /* the file to read from/write to */ 1255 | 1256 | data->DATA_SOCK_STATUS = ' '; /* either " " (none), "L" (listening), "C" (connecting), "F" failed, "A" (active) */ 1257 | data->DATA_SOCK_FAILURE_REASON = 0; 1258 | 1259 | data->XFER = 0; /* either 0 (no xfer), "R" (reading from disk), "W" (writing to disk) */ 1260 | 1261 | /* two buffers are needed for ascii mode translation */ 1262 | data->DATA_SOCK_BUF = (char *)malloc ((MAXBUFLEN * 2) + 1); 1263 | data->DATA_SOCK_BUF [0] = 0; /* buffer near the network connection */ 1264 | data->DATA_SOCK_BUF_LEN = 0; 1265 | 1266 | data->CWD = (char *)malloc (PATHLENGTH); 1267 | strcpy (data->CWD, "/"); 1268 | 1269 | return (data); 1270 | }; 1271 | 1272 | /* 1273 | # closes DATA_SOCK, DATA_LISTEN_SOCK, FILE, and removes their callbacks 1274 | # also resets XFER, DATA_SOCK_BUF, and DATA_SOCK_STATUS 1275 | # (takes an ftp_data hash) 1276 | */ 1277 | static void reset_data_connection (data_tag_ptr data) { 1278 | 1279 | remove_callbacks_f (data->FILE); 1280 | if (data->FILE) { 1281 | #ifdef debug_output 1282 | printf ("Closing down file: %d\n", (int)data->FILE); 1283 | #endif 1284 | fclose (data->FILE); 1285 | }; 1286 | data->FILE = NULL; 1287 | 1288 | remove_callbacks (data->DATA_SOCK); 1289 | 1290 | if (data->DATA_SOCK != -1) { 1291 | #ifdef debug_output 1292 | printf ("Closing down socket: %d\n", data->DATA_SOCK); 1293 | #endif 1294 | closesocket (data->DATA_SOCK); 1295 | }; 1296 | 1297 | data->DATA_SOCK = -1; 1298 | 1299 | remove_callbacks (data->DATA_LISTEN_SOCK); 1300 | 1301 | if (data->DATA_LISTEN_SOCK != -1) { 1302 | #ifdef debug_output 1303 | printf ("Closing down socket: %d\n", data->DATA_LISTEN_SOCK); 1304 | #endif 1305 | closesocket (data->DATA_LISTEN_SOCK); 1306 | } 1307 | 1308 | data->DATA_LISTEN_SOCK = -1; 1309 | 1310 | if (data->DATA_CONNECT_ADDR) 1311 | free (data->DATA_CONNECT_ADDR); 1312 | data->DATA_CONNECT_ADDR = NULL; 1313 | 1314 | data->DATA_SOCK_STATUS = ' '; 1315 | data->DATA_SOCK_FAILURE_REASON = 0; 1316 | data->XFER = 0; 1317 | data->DATA_SOCK_BUF [0] = 0; 1318 | data->DATA_SOCK_BUF_LEN = 0; 1319 | }; 1320 | 1321 | static void file_wcb (FILE * file, data_tag_ptr data) { 1322 | unsigned long i; 1323 | long status; 1324 | char tstr [60]; 1325 | char * s; 1326 | char * t; 1327 | char acrlf [3]; 1328 | 1329 | if (data->DATA_SOCK_BUF_LEN) { 1330 | 1331 | /* ASCII translation */ 1332 | if (data->TYPE == 'A') { 1333 | 1334 | acrlf [0] = 13; 1335 | acrlf [1] = 10; /* Convert CRLFs into just LFs */ 1336 | acrlf [2] = 0; 1337 | 1338 | t = data->DATA_SOCK_BUF; 1339 | 1340 | /* SPECIAL CASE: When a CRLF pair is split over two buffers... */ 1341 | if (t [data->DATA_SOCK_BUF_LEN - 1] == acrlf [0]) { 1342 | data->DATA_SOCK_BUF_LEN--; /* remove the split control character */ 1343 | }; 1344 | 1345 | status = 0; 1346 | ascii2ebcdic (acrlf, 2); 1347 | ascii2ebcdic (t, data->DATA_SOCK_BUF_LEN); 1348 | 1349 | t [data->DATA_SOCK_BUF_LEN] = 0; /* assume no embedded nulls */ 1350 | s = strstr (t, acrlf); /* any to replace? */ 1351 | while (s) { 1352 | s [0] = acrlf [1]; /* make CR - LF, but don't write the second one */ 1353 | i = (s - t) + 1; /* calculate the right length */ 1354 | status += write_from_buf_f (file, t, i); 1355 | status++; /* make up for the missing LF now */ 1356 | 1357 | t = s + 2; /* just past the two control characters... */ 1358 | s = strstr (t, acrlf); 1359 | }; 1360 | status += write_from_buf_f (file, t, strlen (t)); /* embedded nulls not allowed! */ 1361 | 1362 | } else { 1363 | 1364 | status = write_from_buf_f (file, data->DATA_SOCK_BUF, data->DATA_SOCK_BUF_LEN); 1365 | 1366 | }; 1367 | 1368 | if (status != data->DATA_SOCK_BUF_LEN) { 1369 | printf ("error writing to file!\n"); 1370 | sprintf (tstr, "425 Data connection failed: %d", errno); 1371 | ctl_write_line (data, tstr); 1372 | reset_data_connection (data); 1373 | return; 1374 | }; 1375 | 1376 | data->DATA_SOCK_BUF_LEN = 0; 1377 | }; 1378 | 1379 | if (data->DATA_SOCK == -1) { 1380 | /* if there's no socket open to get more data from, the transfer is done */ 1381 | reset_data_connection (data); 1382 | } else { 1383 | enable_read_callback (data->DATA_SOCK); 1384 | disable_write_callback_f (data->FILE); 1385 | }; 1386 | }; 1387 | 1388 | static void file_rcb (FILE * file, data_tag_ptr data) { 1389 | long status; 1390 | 1391 | /* don't let the DATA_SOCK_BUF get full */ 1392 | if (data->DATA_SOCK_BUF_LEN > MAXBUFLEN) { 1393 | disable_read_callback_f (file); 1394 | return; 1395 | } 1396 | 1397 | status = read_into_buf_f (file, &(data->DATA_SOCK_BUF [data->DATA_SOCK_BUF_LEN])); 1398 | 1399 | if (data->DATA_SOCK != -1) { 1400 | enable_write_callback (data->DATA_SOCK); 1401 | }; 1402 | 1403 | /* if there's an EOF (or an error, for now !!!), clean up */ 1404 | if (status <= 0) { 1405 | remove_callbacks_f (file); 1406 | if (file) { 1407 | #ifdef debug_output 1408 | printf ("Closing down file: %d\n", (int)file); 1409 | #endif 1410 | fclose (file); 1411 | }; 1412 | data->FILE = NULL; 1413 | 1414 | if (data->DATA_SOCK != -1) { 1415 | data->XFER = 0; 1416 | }; 1417 | 1418 | return; 1419 | }; 1420 | 1421 | /* ASCII translation */ 1422 | if (data->TYPE == 'A') 1423 | ebcdic2ascii (&(data->DATA_SOCK_BUF [data->DATA_SOCK_BUF_LEN]), status); 1424 | 1425 | data->DATA_SOCK_BUF_LEN += status; 1426 | }; 1427 | 1428 | /* 1429 | # writes data out on the data connection from a buffer 1430 | */ 1431 | static void data_sock_wcb (SOCKET data_sock, data_tag_ptr data) { 1432 | long status; 1433 | char tstr [60]; 1434 | 1435 | if (data->DATA_SOCK_BUF_LEN) { 1436 | status = write_from_buf (data_sock, data->DATA_SOCK_BUF, data->DATA_SOCK_BUF_LEN); 1437 | 1438 | if (status <= 0) { 1439 | printf ("error writing to data connection!\n"); 1440 | sprintf (tstr, "425 Data connection failed: %d", HercGetLastError ()); 1441 | ctl_write_line (data, tstr); 1442 | reset_data_connection (data); 1443 | return; 1444 | }; 1445 | 1446 | data->DATA_SOCK_BUF_LEN -= status; 1447 | }; 1448 | 1449 | /* if there is a file, enable its callback 1450 | (it may be disabled because DATA_SOCK_BUF is full) */ 1451 | if (data->FILE) { 1452 | enable_read_callback_f (data->FILE); 1453 | }; 1454 | 1455 | if (data->DATA_SOCK_BUF_LEN == 0) { 1456 | if (data->FILE == NULL) { 1457 | /* if there's no file open to get more data from, the transfer is done */ 1458 | ctl_write_line (data, "226 Transfer complete!"); 1459 | reset_data_connection (data); 1460 | } else { 1461 | disable_write_callback (data_sock); 1462 | }; 1463 | }; 1464 | }; 1465 | 1466 | /* 1467 | # read data from the data connection 1468 | */ 1469 | static void data_sock_rcb (SOCKET data_sock, data_tag_ptr data) { 1470 | long status; 1471 | 1472 | status = read_into_buf (data_sock, &(data->DATA_SOCK_BUF [data->DATA_SOCK_BUF_LEN])); 1473 | 1474 | /* wake up the disk writer */ 1475 | if (data->FILE) { 1476 | enable_write_callback_f (data->FILE); 1477 | }; 1478 | 1479 | /* if there's an EOF (or an error, for now !!!), signal that the transfer is completed */ 1480 | if (status <= 0) { 1481 | ctl_write_line (data, "226 Transfer complete."); 1482 | remove_callbacks (data_sock); 1483 | 1484 | if (data->DATA_SOCK != -1) { 1485 | #ifdef debug_output 1486 | printf ("Closing down socket: %d\n", data->DATA_SOCK); 1487 | #endif 1488 | closesocket (data->DATA_SOCK); 1489 | }; 1490 | 1491 | data->DATA_SOCK = -1; 1492 | 1493 | data->XFER = 0; 1494 | 1495 | return; 1496 | }; 1497 | 1498 | data->DATA_SOCK_BUF_LEN += status; 1499 | 1500 | /* if there's too much data, wait for it to thin out */ 1501 | if (data->DATA_SOCK_BUF_LEN > MAXBUFLEN) { 1502 | disable_read_callback (data_sock); 1503 | }; 1504 | }; 1505 | 1506 | /* 1507 | # sets a callback for the data connection, depending on the transfer status 1508 | # only sets a callback if there is an active data sock 1509 | */ 1510 | static void set_xfer_callback (data_tag_ptr data) { 1511 | 1512 | if (data->DATA_SOCK_STATUS == 'A') { 1513 | if (data->DATA_SOCK == -1) return; 1514 | 1515 | if (data->XFER == 'R') { 1516 | /* reading from disk means writing to the network */ 1517 | set_callbacks (data->DATA_SOCK, data, NULL, &data_sock_wcb); 1518 | } else if (data->XFER == 'W') { 1519 | /* writing to disk means reading from the network */ 1520 | set_callbacks (data->DATA_SOCK, data, &data_sock_rcb, NULL); 1521 | } else { 1522 | /* if not yet transferring, just wait 1523 | and the callback will be put in once the connection is made */ 1524 | remove_callbacks (data->DATA_SOCK); 1525 | }; 1526 | }; 1527 | if (data->DATA_SOCK_STATUS == 'C') { 1528 | /* if not yet active, just wait 1529 | and the callback will be put in once the connection is made */ 1530 | remove_callbacks (data->DATA_SOCK); 1531 | }; 1532 | }; 1533 | 1534 | /* 1535 | # helper for the two functions below 1536 | */ 1537 | static void prepare_data_sock (data_tag_ptr data) { 1538 | long data_conn_status; 1539 | long i; 1540 | char tstr [60]; 1541 | 1542 | if (data->DATA_SOCK_STATUS == 'C') { 1543 | data_conn_status = check_sock_connect (data->DATA_SOCK, data->DATA_CONNECT_ADDR); 1544 | } else { 1545 | if (data->DATA_SOCK != -1) { 1546 | data_conn_status = 1; 1547 | } else { 1548 | data_conn_status = 0; 1549 | }; 1550 | }; 1551 | 1552 | /* !!! reimpliment separately for listening and connecting socket */ 1553 | if (!data_conn_status) { 1554 | printf ("connect failed.\n"); 1555 | 1556 | /* on failure, either respond immediately if a transfer is pending 1557 | or queue up the error */ 1558 | if (data->XFER) { 1559 | sprintf (tstr, "425 Can't open data connection: %d", HercGetLastError ()); 1560 | generic_write_line (data->SOCK, &(data->WRITE_BUF [data->WRITE_BUF_LENGTH]), tstr); 1561 | data->WRITE_BUF_LENGTH = strlen (data->WRITE_BUF); 1562 | reset_data_connection (data); 1563 | } else { 1564 | i = HercGetLastError (); 1565 | printf ("queueing failure for later reference: %d\n", i); 1566 | reset_data_connection (data); 1567 | data->DATA_SOCK_STATUS = 'F'; 1568 | data->DATA_SOCK_FAILURE_REASON = i; 1569 | }; 1570 | 1571 | } else { 1572 | /* if the connection succeeded, set DATA_SOCK_STATUS to active */ 1573 | data->DATA_SOCK_STATUS = 'A'; 1574 | /* don't need to send anything to the control connection 1575 | it always has something appropriate to say when a transfer starts */ 1576 | set_xfer_callback (data); 1577 | }; 1578 | }; 1579 | 1580 | /* 1581 | # accepts incoming connections on DATA_LISTEN_SOCK 1582 | */ 1583 | static void data_listen_sock_rcb (SOCKET data_listen_sock, data_tag_ptr data) { 1584 | 1585 | data->DATA_SOCK = accept_sock (data_listen_sock); 1586 | 1587 | /* check if sock accepted properly */ 1588 | 1589 | if (data->DATA_SOCK == -1) { 1590 | printf ("error accepting data listen sock!\n"); 1591 | } else { 1592 | prepare_data_sock (data); 1593 | } 1594 | 1595 | /* !!! set callbacks */ 1596 | 1597 | remove_callbacks (data_listen_sock); 1598 | 1599 | if (data->DATA_LISTEN_SOCK != -1) { 1600 | #ifdef debug_output 1601 | printf ("Closing down socket: %d\n", data->DATA_LISTEN_SOCK); 1602 | #endif 1603 | closesocket (data->DATA_LISTEN_SOCK); 1604 | }; 1605 | 1606 | data->DATA_LISTEN_SOCK = -1; 1607 | }; 1608 | 1609 | /* 1610 | # waits for DATA_SOCK to connect 1611 | */ 1612 | static void data_connect_sock_wcb (SOCKET data_connect_sock, data_tag_ptr data) { 1613 | 1614 | /* !!! check if connection successful */ 1615 | 1616 | prepare_data_sock (data); 1617 | }; 1618 | 1619 | static void ctl_write_msg (data_tag_ptr data, long code, char * message) { 1620 | char * to_write; 1621 | 1622 | to_write = ftp_server_msg (code, message); 1623 | 1624 | generic_write (data->SOCK, &(data->WRITE_BUF [data->WRITE_BUF_LENGTH]), to_write); 1625 | data->WRITE_BUF_LENGTH = strlen (data->WRITE_BUF); 1626 | 1627 | free (to_write); 1628 | }; 1629 | 1630 | 1631 | 1632 | /* 1633 | # takes a path name, and removes any extra slashes and trailing slashes, 1634 | # and resolves . and .. 1635 | */ 1636 | static char * canonical_path (char * path) { 1637 | char * a; 1638 | char * b; 1639 | char * c; 1640 | char * result; 1641 | long i; 1642 | 1643 | result = (char *)malloc (PATHLENGTH); 1644 | 1645 | c = path; 1646 | while ((c [0] == '\\') || (c [0] == '/')) 1647 | c++; /* Must start with a (back)slash */ 1648 | 1649 | result [0] = 0; 1650 | 1651 | a = strstr (c, "\\"); 1652 | b = strstr (c, "/"); 1653 | if ((a == NULL) || 1654 | ((b != NULL) && (b < a))) { 1655 | a = b; 1656 | }; 1657 | 1658 | while (a) { 1659 | a [0] = 0; 1660 | if (c [0]) { 1661 | if (strcmp (c, ".") != 0) { 1662 | 1663 | if (strcmp (c, "..") == 0) { 1664 | i = strlen (result); 1665 | while (i) { 1666 | if (result [--i] == '/') { 1667 | result [i] = 0; 1668 | break; 1669 | }; 1670 | }; 1671 | } else { 1672 | strcat (result, "/"); 1673 | strcat (result, c); 1674 | }; 1675 | }; 1676 | c = a; 1677 | }; 1678 | c++; 1679 | 1680 | a = strstr (c, "\\"); 1681 | b = strstr (c, "/"); 1682 | if ((a == NULL) || 1683 | ((b != NULL) && (b < a))) { 1684 | a = b; 1685 | }; 1686 | }; 1687 | 1688 | if (strcmp (c, "..") == 0) { 1689 | i = strlen (result); 1690 | while (i) { 1691 | if (result [--i] == '/') break; 1692 | }; 1693 | result [i++] = '/'; 1694 | result [i] = 0; 1695 | } else { 1696 | strcat (result, "/"); 1697 | if (strcmp (c, ".") != 0) strcat (result, c); 1698 | if ((c [0]) && (strcmp (c, ".") != 0)) 1699 | strcat (result, "/"); 1700 | }; 1701 | 1702 | free (path); 1703 | 1704 | return (result); 1705 | }; 1706 | 1707 | /* 1708 | # takes two args, cwd and cd_into 1709 | # treats cwd as the current directory, and changes it to the result 1710 | # of cd'ing from $cwd into $cd_into 1711 | # always returns a canonical path (no .'s, ..'s, or extra slashes) 1712 | # if cd_into starts with a /, just gives cd_into 1713 | # if cd_into is undefined, just gives cwd 1714 | */ 1715 | static char * dir_concat (char * cwd, char * cd_into) { 1716 | char * result; 1717 | 1718 | result = (char *)malloc(PATHLENGTH); 1719 | 1720 | if (cd_into == NULL) { 1721 | strcpy (result, cwd); 1722 | return (result); 1723 | 1724 | } else if ((cd_into [0] == '/') || 1725 | (cd_into [0] == '\\')) { 1726 | strcpy (result, cd_into); 1727 | 1728 | } else { 1729 | sprintf (result, "%s%s", cwd, cd_into); 1730 | }; 1731 | 1732 | return (canonical_path (result)); 1733 | }; 1734 | 1735 | // New routine to refresh the file-list on an MVS system 1736 | static void refresh () { 1737 | p_root r; 1738 | p_root d; 1739 | int i; 1740 | int j; 1741 | 1742 | numdevs = 0; 1743 | i = dirsupport (DEFAULT_PARMLIB); // Re-read the VATLST00 file (not really needed) 1744 | if (i == 0) { 1745 | r = (p_root)malloc (sizeof (t_root)); 1746 | if (r == NULL) { 1747 | printf ("Out of memory refreshing, vtoc stage\n"); 1748 | return; 1749 | } 1750 | r->dircount = 0; 1751 | r->next = NULL; 1752 | j = 0; 1753 | while (j < numdevs) { 1754 | i += readvtoc (devices [j], units [j], r); 1755 | j++; 1756 | } 1757 | } 1758 | if (i == 0) { 1759 | d = root; 1760 | root = r; // Update the real root 1761 | while (d) { // Clean-up old contents 1762 | r = d; 1763 | d = d->next; 1764 | free (r); 1765 | } 1766 | } 1767 | } 1768 | 1769 | /* 1770 | # takes an ftp_data, and a directory 1771 | # and tries to CD into the given directory 1772 | # returns 1 on success, 1773 | # and a -1 if the directory doesn't exist 1774 | # or -2 if it's not really a directory 1775 | */ 1776 | static long ftp_chdir (data_tag_ptr data, char * cd_into) { 1777 | char * new_cwd; 1778 | char * s; 1779 | char real_new_cwd [PATHLENGTH]; 1780 | FILE * fh; 1781 | long org; 1782 | 1783 | new_cwd = dir_concat (data->CWD, cd_into); 1784 | 1785 | if (strcmp (new_cwd, "/") == 0) { 1786 | strcpy (data->CWD, new_cwd); 1787 | free (new_cwd); 1788 | return (1); 1789 | }; 1790 | 1791 | strcpy (real_new_cwd, &(new_cwd [1])); /* remove slash */ 1792 | s = strstr (real_new_cwd, "/"); 1793 | s [0] = 0; 1794 | 1795 | org = getorg (real_new_cwd); 1796 | if (org == 0) { // File wasn't found 1797 | refresh (); 1798 | org = getorg (real_new_cwd); // try again now 1799 | } 1800 | 1801 | if (org == 2) { 1802 | strcpy (data->CWD, new_cwd); 1803 | free (new_cwd); 1804 | return (1); 1805 | }; 1806 | 1807 | free (new_cwd); 1808 | 1809 | if (org == 1) { 1810 | return (-2); 1811 | }; 1812 | 1813 | return (-1); 1814 | }; 1815 | 1816 | /* 1817 | # writes a message, based on the status of the data connection 1818 | # if the data connection is viable (listening, connecting, or active) 1819 | # returns 1 1820 | # otherwise returns 0, and resets the data connection 1821 | */ 1822 | static long prepare_for_xfer (data_tag_ptr data) { 1823 | char status; 1824 | char tstr [40]; 1825 | 1826 | status = data->DATA_SOCK_STATUS; 1827 | 1828 | if (status == 'A') { 1829 | ctl_write_line (data, "125 Data connection open, starting transfer"); 1830 | return (1); 1831 | 1832 | } else if ((status == 'L') || (status == 'C')) { 1833 | ctl_write_line (data, "150 Now opening data connection"); 1834 | return (1); 1835 | 1836 | } else { 1837 | if (status == 'F') { 1838 | sprintf (tstr, "425 Data connection failed: %d", data->DATA_SOCK_FAILURE_REASON); 1839 | ctl_write_line (data, tstr); 1840 | } else { 1841 | ctl_write_line (data, "425 No data connection established."); 1842 | }; 1843 | reset_data_connection (data); 1844 | return (0); 1845 | }; 1846 | }; 1847 | 1848 | static char * mystrcat (char * dest, char * src, long * clen, long * j) { /* j is always the 'end' of dest */ 1849 | long i; 1850 | 1851 | i = strlen (src); 1852 | 1853 | if (*j + i >= *clen) { 1854 | *clen = *clen * 2; /* Just keep doubling the buffer until it all fits */ 1855 | dest = (char *)realloc (dest, *clen + 1); 1856 | }; 1857 | 1858 | strcpy (&(dest [*j]), src); 1859 | 1860 | *j += i; 1861 | 1862 | return (dest); 1863 | }; 1864 | 1865 | /* Special (Non-PDS) DD names that can be accessed within FTPD */ 1866 | /* This is primarily intended for DD INTRDR to be added to the FTP PROC */ 1867 | #define added_ddl 10 // Allow up to 10 user-defined DDs 1868 | int added_ddc = 0; 1869 | char added_dds [added_ddl][9]; 1870 | 1871 | /* 1872 | # returns a listing for the given file or directory, like `ls -l` 1873 | -rw-r--r-- 1 Administ None 5846 May 14 18:34 w32ctca.o 1874 | -rwxrwxrwx 1 Administ None 23407 Jan 8 12:46 xstore.c 1875 | -rw-r--r-- 1 Administ None 27158 May 14 18:37 xstore.o 1876 | -rwxrwxrwx 1 Administ Users 3509 Aug 5 2002 znew 1877 | */ 1878 | static char * ls_dash_l (char * target, unsigned int acee) { 1879 | long i; 1880 | long j; /* current length of result */ 1881 | long clen; 1882 | char * s; 1883 | char * result; 1884 | char line [PATHLENGTH]; 1885 | char name [PATHLENGTH]; 1886 | long size = 0; 1887 | long type; 1888 | long stat [2]; 1889 | 1890 | char fmtdate [80]; 1891 | time_t ltime; 1892 | struct tm * today; 1893 | 1894 | p_root r; 1895 | 1896 | clen = MAXBUFLEN * 2; 1897 | result = (char *)malloc ((MAXBUFLEN * 2) + 1); 1898 | 1899 | sprintf (result, "total %05d\r\n", 0); /* Make space for 'total' */ 1900 | j = strlen (result); 1901 | 1902 | stat [0] = 1; 1903 | stat [1] = 1024; 1904 | 1905 | if (strcmp (target, "/") == 0) { /* Root */ 1906 | 1907 | if (added_ddc) { // If special DDs are in use, set up a dummy date 1908 | time (<ime); // Use NOW as the date. 1909 | today = localtime (<ime); 1910 | strftime (fmtdate, 40, "%b %d %Y", today); 1911 | i = 0; 1912 | while (i < added_ddc) { // Add the user DD list to the output 1913 | size += stat[1]; 1914 | type = '-'; 1915 | sprintf (line, "%cr-xr-xr-x %3d %-8s %-8s %8d %12s %s\r\n", 1916 | type, stat[0], FAKE_USER, FAKE_GROUP, stat[1], fmtdate, added_dds [i]); 1917 | 1918 | result = mystrcat (result, line, &clen, &j); 1919 | i++; 1920 | } 1921 | } 1922 | 1923 | // refresh (); Comment-out for faster directory refreshes (using the cache) 1924 | // Then, to update the listing (if required) use the "REFR" command or ftpcmd.c 1925 | 1926 | r = root; // Start point 1927 | while (r) { 1928 | i = 0; 1929 | while (i < r->dircount) { 1930 | size += stat[1]; 1931 | 1932 | if (r->directories [i].org == 1) { 1933 | type = '-'; 1934 | } else { 1935 | type = 'd'; 1936 | } 1937 | 1938 | ltime = r->directories [i].date; 1939 | today = localtime (<ime); 1940 | strftime (fmtdate, 40, "%b %d %Y", today); 1941 | 1942 | sprintf (line, "%cr-xr-xr-x %3d %-8s %-8s %8d %12s %s\r\n", 1943 | type, stat[0], FAKE_USER, FAKE_GROUP, stat[1], fmtdate, r->directories [i].name); 1944 | 1945 | result = mystrcat (result, line, &clen, &j); 1946 | 1947 | i++; 1948 | } 1949 | r = r->next; 1950 | } 1951 | } else { /* PDS */ 1952 | 1953 | strcpy (line, &(target [1])); 1954 | s = strstr (line, "/"); 1955 | s [0] = 0; 1956 | strupr (line); 1957 | 1958 | sprintf (name, "//DSN:%s", line); 1959 | readdir (name, acee); /* Go get 'em */ 1960 | 1961 | r = root; // Start point, search for the right date to transmit 1962 | while (r) { 1963 | i = 0; 1964 | while (i < r->dircount) { 1965 | if (stricmp (r->directories [i].name, line) == 0) { 1966 | ltime = r->directories [i].date; 1967 | today = localtime (<ime); 1968 | strftime (fmtdate, 40, "%b %d %Y", today); 1969 | break; 1970 | } 1971 | i++; 1972 | } 1973 | if (i < r->dircount) 1974 | break; 1975 | r = r->next; 1976 | } 1977 | if (!r) { // Couldn't find the right date... 1978 | time (<ime); // Use NOW as the date. 1979 | today = localtime (<ime); 1980 | strftime (fmtdate, 40, "%b %d %Y", today); 1981 | } 1982 | 1983 | i = 0; 1984 | while (i < pdsecount) { 1985 | size += stat[1]; 1986 | 1987 | sprintf (line, "-r-xr-xr-x %3d %-8s %-8s %8d %12s %s\r\n", 1988 | stat[0], FAKE_USER, FAKE_GROUP, stat[1], fmtdate, pdsename [i]); 1989 | 1990 | result = mystrcat (result, line, &clen, &j); 1991 | 1992 | i++; 1993 | }; 1994 | }; 1995 | 1996 | sprintf (line, "total %05d\r\n", size / 1024); /* Max: 1000 => '01000' */ 1997 | i = strlen (line); 1998 | strncpy (result, line, i); 1999 | 2000 | return (result); 2001 | }; 2002 | 2003 | /* 2004 | # returns a listing for the given directory 2005 | */ 2006 | static char * ftp_ls (data_tag_ptr data) { 2007 | 2008 | return ls_dash_l (data->CWD, data->ACEE); 2009 | }; 2010 | 2011 | static long get_cmd_args (char * line, char * cmd, char * args) { 2012 | char * a; 2013 | 2014 | if ((line == NULL) || (line [0] == 0)) return (0); 2015 | 2016 | a = strstr (line, " "); 2017 | if (a) { 2018 | a [0] = 0; 2019 | strncpy (cmd, line, PATHLENGTH); 2020 | a [0] = ' '; 2021 | a++; 2022 | strncpy (args, a, PATHLENGTH); 2023 | } else { 2024 | strncpy (cmd, line, 39); 2025 | cmd [39] = 0; 2026 | args [0] = 0; 2027 | }; 2028 | 2029 | strupr (cmd); 2030 | 2031 | return (1); 2032 | }; 2033 | 2034 | 2035 | /* 2036 | # read in new data on the control connection, and handle any pending commands 2037 | */ 2038 | static void ftp_sock_rcb (SOCKET sock, data_tag_ptr data) { 2039 | long status; 2040 | struct sockaddr_in addr; 2041 | long i; 2042 | long j; 2043 | char * line; 2044 | char * s; 2045 | char * comma_list; 2046 | char cmd [40]; 2047 | char args [PATHLENGTH]; 2048 | char tstr [PATHLENGTH]; 2049 | char real_path [PATHLENGTH]; 2050 | char * tpath; 2051 | char * tdata; 2052 | char type; 2053 | FILE * fh; 2054 | SOCKET tsock; 2055 | char fmode [19]; 2056 | int org; 2057 | short pasv_port; 2058 | unsigned int acee_old; 2059 | 2060 | /* get whatever data is ready */ 2061 | status = read_into_buf (sock, &(data->READ_BUF [data->READ_BUF_LENGTH])); 2062 | 2063 | /* close the connection on error */ 2064 | if (status <= 0) { 2065 | printf ("closing connection due to unexpected EOF\n"); 2066 | reset_data_connection (data); 2067 | 2068 | tsock = data->SOCK; 2069 | 2070 | remove_callbacks (data->SOCK); /* Should delete 'data' */ 2071 | 2072 | if (tsock != -1) { 2073 | #ifdef debug_output 2074 | printf ("Closing down socket: %d\n", tsock); 2075 | #endif 2076 | closesocket (tsock); 2077 | }; 2078 | 2079 | return; 2080 | } 2081 | 2082 | ascii2ebcdic (&(data->READ_BUF [data->READ_BUF_LENGTH]), status); 2083 | 2084 | #ifdef debug_output 2085 | data->READ_BUF [data->READ_BUF_LENGTH + status] = 0; 2086 | printf ("Recv %d bytes: %s\n", status, &(data->READ_BUF [data->READ_BUF_LENGTH])); 2087 | #endif 2088 | 2089 | data->READ_BUF_LENGTH += status; 2090 | 2091 | /* don't handle a command if busy! */ 2092 | 2093 | /* what the heck, handle a command */ 2094 | while (line = grab_line (data->READ_BUF, &data->READ_BUF_LENGTH)) { 2095 | 2096 | /* need to keep the whole line, for pushback */ 2097 | 2098 | #ifdef debug_output 2099 | printf ("Processing line: %s\n", line); 2100 | #endif 2101 | 2102 | if (get_cmd_args (line, cmd, args)) { 2103 | 2104 | #ifdef debug_output 2105 | printf ("Processing command: %s <%s>\n", cmd, args); 2106 | #endif 2107 | 2108 | if (strstr (cmd, "ABOR") != NULL) { 2109 | /* ABOR usually comes with telnet control characters 2110 | that should be stripped out */ 2111 | reset_data_connection (data); 2112 | ctl_write_line (data, "226 Closing data connection"); 2113 | } else if (strcmp (cmd, "QUIT") == 0) { 2114 | reset_data_connection (data); 2115 | data->DONE = 1; 2116 | disable_read_callback (sock); 2117 | ctl_write_line (data, "221 Bye!"); 2118 | /* the ftp_data will be deleted once all its callbacks 2119 | are done */ 2120 | } else if (data->XFER) { 2121 | printf ("not going to handle commands while transferring!\n"); 2122 | /* if transferring, don't handle any other commands */ 2123 | i = strlen (line) + 2; 2124 | memmove (&(data->READ_BUF [i]), data->READ_BUF, data->READ_BUF_LENGTH); 2125 | data->READ_BUF_LENGTH += i; 2126 | memcpy (data->READ_BUF, line, i - 2); 2127 | data->READ_BUF [i - 2] = '\r'; 2128 | data->READ_BUF [i - 1] = '\n'; 2129 | free (line); 2130 | return; 2131 | } else if (strcmp (cmd, "USER") == 0) { 2132 | if (data->ACEE > 1) user_logout (data->ACEE, data->USER); 2133 | data->ACEE = 0; 2134 | args [8] = 0; 2135 | _strupr (args); 2136 | strcpy (data->USER, args); 2137 | ctl_write_line (data, "331 Okay, waiting for password."); 2138 | } else if (strcmp (cmd, "PASS") == 0) { 2139 | args [8] = 0; 2140 | data->ACEE = rac_user_login (data->USER, args); 2141 | if (data->ACEE > 1) { 2142 | ctl_write_msg (data, 230, LOGIN_MESSAGE); 2143 | time (<); 2144 | td = localtime (<); 2145 | sprintf (wtomsg, LOGIN_MESSAGE_C, data->USER, (* td).tm_hour, (* td).tm_min, (* td).tm_sec, (* td).tm_year + 1900, (* td).tm_mon + 1, (* td).tm_mday); 2146 | _write2op (wtomsg); 2147 | } 2148 | if (data->ACEE == 1) ctl_write_msg (data, 230, LOGIN_MESSAGE); 2149 | if (data->ACEE == 0) { 2150 | ctl_write_msg (data, 530, LOGIN_FAILED_MESSAGE); 2151 | Sleep (1000); 2152 | } 2153 | } else if (strcmp (cmd, "NOOP") == 0) { 2154 | ctl_write_line (data, "200 NOOP command successful"); 2155 | } else if (!data->ACEE) { 2156 | // don't allow other commands until logged in 2157 | ctl_write_line (data, "530 Log in with USER and PASS first."); 2158 | } else if (strcmp (cmd, "SYST") == 0) { 2159 | ctl_write_line (data, "215 MVS Type: L8"); 2160 | } else if (strcmp (cmd, "MODE") == 0) { 2161 | if ((args [0] == 'S') || (args [0] == 's')) { 2162 | ctl_write_line (data, "200 MODE S."); 2163 | } else { 2164 | ctl_write_line (data, "502 Unimplimented MODE type"); 2165 | }; 2166 | } else if (strcmp (cmd, "STRU") == 0) { 2167 | if ((args [0] == 'F') || (args [0] == 'f')) { 2168 | ctl_write_line (data, "200 STRU F."); 2169 | } else { 2170 | ctl_write_line (data, "504 Unimplimented STRU type"); 2171 | }; 2172 | } else if (strcmp (cmd, "TYPE") == 0) { 2173 | type = toupper (args [0]); 2174 | if ((type == 'A') || (type == 'I')) { 2175 | data->TYPE = type; 2176 | sprintf (tstr, "200 Type set to %c", type); 2177 | ctl_write_line (data, tstr); 2178 | } else { 2179 | sprintf (tstr, "504 Type %c not implemented", type); 2180 | ctl_write_line (data, tstr); 2181 | }; 2182 | } else if (strcmp (cmd, "PASV") == 0) { 2183 | reset_data_connection (data); 2184 | /* listen on the same address as the client connected to */ 2185 | /* ***************************************** */ 2186 | 2187 | // PORT CODE GOES HERE 2188 | // If we're not using a specific range let the stack pick one 2189 | if (PASV_START_PORT == 0) { 2190 | data->DATA_LISTEN_SOCK = listen_sock (0, sock_ip (sock)); 2191 | } else { 2192 | pasv_port = (rand() % (PASV_END_PORT - PASV_START_PORT + 1)) + PASV_START_PORT; 2193 | data->DATA_LISTEN_SOCK = listen_sock (pasv_port, sock_ip (sock)); 2194 | } 2195 | 2196 | if (data->DATA_LISTEN_SOCK == -1) { 2197 | ctl_write_line (data, "500 PASV command unsuccessful"); 2198 | } else { 2199 | data->DATA_SOCK_STATUS = 'L'; 2200 | set_callbacks (data->DATA_LISTEN_SOCK, data, &data_listen_sock_rcb, NULL); 2201 | 2202 | comma_list = sockaddr_to_comma_list (data->DATA_LISTEN_SOCK); 2203 | sprintf (tstr, "227 Entering Passive Mode (%s)", comma_list); 2204 | ctl_write_line (data, tstr); 2205 | free (comma_list); 2206 | }; 2207 | 2208 | } else if (strcmp (cmd, "PORT") == 0) {ctl_write_line (data, "500 PORT command rejected, please use passive mode"); 2209 | /* if (!comma_list_to_sockaddr (&addr, args)) { 2210 | ctl_write_line (data, "500 That's not how to use the PORT command."); 2211 | } else { 2212 | reset_data_connection (data); 2213 | data->DATA_CONNECT_ADDR = malloc (sizeof (addr)); 2214 | memcpy (data->DATA_CONNECT_ADDR, &addr, sizeof (addr)); 2215 | data->DATA_SOCK = nb_connect_sock ((void *)&addr, &i); 2216 | 2217 | if (data->DATA_SOCK == -1) { 2218 | sprintf (tstr, "500 PORT command unsuccessful cd: %d", i); 2219 | ctl_write_line (data, tstr); 2220 | } else { 2221 | data->DATA_SOCK_STATUS = 'C'; 2222 | set_callbacks (data->DATA_SOCK, data, NULL, &data_connect_sock_wcb); 2223 | ctl_write_line (data, "200 PORT command successful"); 2224 | }; 2225 | }; */ 2226 | } else if (strcmp (cmd, "LIST") == 0) { 2227 | if (prepare_for_xfer (data)) { 2228 | tdata = ftp_ls (data); 2229 | if (tdata) { 2230 | free (data->DATA_SOCK_BUF); 2231 | data->DATA_SOCK_BUF = tdata; 2232 | } else { 2233 | data->DATA_SOCK_BUF [0] = 0; 2234 | }; 2235 | 2236 | data->DATA_SOCK_BUF_LEN = strlen (data->DATA_SOCK_BUF); 2237 | 2238 | if (data->DATA_SOCK_BUF_LEN) { 2239 | #ifdef debug_output 2240 | printf ("Directory: %s\n", data->DATA_SOCK_BUF); 2241 | #endif 2242 | ebcdic2ascii (data->DATA_SOCK_BUF, data->DATA_SOCK_BUF_LEN); 2243 | }; 2244 | 2245 | data->XFER = 'R'; /* the client is reading the file listing */ 2246 | set_xfer_callback (data); 2247 | }; 2248 | } else if (strcmp (cmd, "PWD") == 0) { 2249 | sprintf (tstr, "257 \"%s\" is current directory.", data->CWD); 2250 | ctl_write_line (data, tstr); 2251 | } else if (strcmp (cmd, "CWD") == 0) { 2252 | if (ftp_chdir (data, args) == 1) { 2253 | ctl_write_line (data, "250 CWD command successful"); 2254 | } else { 2255 | sprintf (tstr, "550 Can't cd into %s.", args); 2256 | ctl_write_line (data, tstr); 2257 | }; 2258 | } else if (strcmp (cmd, "CDUP") == 0) { // Should not need a dir-refresh on this one 2259 | if (ftp_chdir (data, "..")) { 2260 | ctl_write_line (data, "250 CDUP command successful"); 2261 | } else { 2262 | ctl_write_line (data, "550 Can't cd into .."); 2263 | }; 2264 | } else if (strcmp (cmd, "RETR") == 0) { 2265 | 2266 | tpath = dir_concat (data->CWD, args); 2267 | strcpy (real_path, "//DSN:"); 2268 | strcpy (&(real_path [6]), &(tpath [1])); /* Remove root slash */ 2269 | strupr (real_path); 2270 | free (tpath); 2271 | 2272 | if (real_path [6] != 0) { 2273 | i = strlen (real_path); 2274 | real_path [i - 1] = 0; /* Remove trailing slash */ 2275 | s = strstr (&(real_path [6]), "/"); 2276 | if (s == NULL) { /* open root file */ 2277 | 2278 | j = 0; // Check if the name matches a known user DD 2279 | while (j < added_ddc) { 2280 | if (strcmp (added_dds [j], &(real_path [6])) == 0) { 2281 | real_path [3] = 'D'; // Convert to //DDN:... 2282 | break; 2283 | } 2284 | j++; 2285 | } 2286 | if (j == added_ddc) { // Not a user DD, continue checking 2287 | 2288 | org = getorg (&(real_path [6])); 2289 | if (org == 0) { // File wasn't found 2290 | refresh (); 2291 | org = getorg (&(real_path [6])); // try again now 2292 | } 2293 | 2294 | if (org != 1) /* First check it's PS */ 2295 | real_path [0] = 0; 2296 | } 2297 | } else { /* open pds file */ 2298 | s [0] = '('; 2299 | real_path [i - 1] = ')'; 2300 | }; 2301 | 2302 | #ifdef debug_output 2303 | if (real_path [0] != 0) printf ("Opening filename: %s (Read)\n", real_path); 2304 | #endif 2305 | 2306 | if (data->TYPE == 'A') { 2307 | strcpy (fmode, "r"); 2308 | } else { 2309 | strcpy (fmode, "rb,vmode=2,umode=0"); 2310 | }; 2311 | 2312 | if (real_path [0] == 0) { 2313 | 2314 | sprintf (tstr, "553 %s: Access denied.", args); 2315 | ctl_write_line (data, tstr); 2316 | 2317 | } else { 2318 | if (data->ACEE > 1) { 2319 | 2320 | rac_ftp_auth (1); 2321 | acee_old = rac_switch_user (data->ACEE); 2322 | } 2323 | 2324 | fh = fopen (real_path, fmode); 2325 | 2326 | if (data->ACEE > 1) { 2327 | 2328 | rac_switch_user (acee_old); 2329 | rac_ftp_auth (0); 2330 | } 2331 | 2332 | if (fh == NULL) { 2333 | 2334 | sprintf (tstr, "550 %s: %d", args, errno); 2335 | ctl_write_line (data, tstr); 2336 | 2337 | } else { 2338 | if (prepare_for_xfer (data)) { 2339 | data->FILE = fh; 2340 | #ifdef debug_output 2341 | printf ("Opened file: %d read\n", (int)data->FILE); 2342 | #endif 2343 | set_callbacks_f (fh, data, file_rcb, NULL); 2344 | data->XFER = 'R'; /* the client is reading the file */ 2345 | set_xfer_callback (data); 2346 | }; 2347 | }; 2348 | }; 2349 | } else { 2350 | sprintf (tstr, "553 root: Access denied."); 2351 | ctl_write_line (data, tstr); 2352 | }; 2353 | } else if (strcmp (cmd, "STOR") == 0) { 2354 | 2355 | tpath = dir_concat (data->CWD, args); 2356 | strcpy (real_path, "//DSN:"); 2357 | strcpy (&(real_path [6]), &(tpath [1])); /* Remove root slash */ 2358 | strupr (real_path); 2359 | free (tpath); 2360 | 2361 | if (real_path [6] != 0) { 2362 | i = strlen (real_path); 2363 | real_path [i - 1] = 0; /* Remove trailing slash */ 2364 | 2365 | s = strstr (&(real_path [6]), "/"); 2366 | if (s == NULL) { /* open root file */ 2367 | 2368 | j = 0; // Check if the name matches a known user DD 2369 | while (j < added_ddc) { 2370 | if (strcmp (added_dds [j], &(real_path [6])) == 0) { 2371 | real_path [3] = 'D'; // Convert to //DDN:... 2372 | break; 2373 | } 2374 | j++; 2375 | } 2376 | if (j == added_ddc) { // Not a user DD, continue checking 2377 | 2378 | org = getorg (&(real_path [6])); 2379 | if (org == 0) { // File wasn't found 2380 | refresh (); 2381 | org = getorg (&(real_path [6])); // try again now 2382 | } 2383 | 2384 | if (org != 1) /* First check it's PS */ 2385 | real_path [0] = 0; 2386 | } 2387 | } else { /* open pds file */ 2388 | s [0] = '('; 2389 | real_path [i - 1] = ')'; 2390 | }; 2391 | 2392 | #ifdef debug_output 2393 | if (real_path [0] != 0) printf ("Opening filename: %s (Write)\n", real_path); 2394 | #endif 2395 | 2396 | if (data->TYPE == 'A') { 2397 | strcpy (fmode, "w"); 2398 | } else { 2399 | strcpy (fmode, "wb,vmode=2,umode=0"); 2400 | }; 2401 | 2402 | if (real_path [0] == 0) { 2403 | 2404 | sprintf (tstr, "553 %s: Access denied.", args); 2405 | ctl_write_line (data, tstr); 2406 | 2407 | } else { 2408 | if (data->ACEE > 1) { 2409 | 2410 | rac_ftp_auth (1); 2411 | acee_old = rac_switch_user (data->ACEE); 2412 | } 2413 | 2414 | fh = fopen (real_path, fmode); 2415 | 2416 | if (data->ACEE > 1) { 2417 | 2418 | rac_switch_user (acee_old); 2419 | rac_ftp_auth (0); 2420 | } 2421 | 2422 | if (fh == NULL) { 2423 | 2424 | sprintf (tstr, "550 %s: Not opened %d", args, errno); 2425 | ctl_write_line (data, tstr); 2426 | 2427 | } else { 2428 | if (prepare_for_xfer (data)) { 2429 | data->FILE = fh; 2430 | #ifdef debug_output 2431 | printf ("Opened file: %d write\n", (int)data->FILE); 2432 | #endif 2433 | set_callbacks_f (fh, data, NULL, file_wcb); 2434 | data->XFER = 'W'; /* the client is writing the file */ 2435 | set_xfer_callback (data); 2436 | }; 2437 | }; 2438 | }; 2439 | } else { 2440 | sprintf (tstr, "553 root: Access denied."); 2441 | ctl_write_line (data, tstr); 2442 | }; 2443 | 2444 | } else if ((strcmp (cmd, "TERMINATE") == 0) && (strcmp (data->USER, AUTH_USER) == 0)) { 2445 | sprintf (wtomsg,GENERIC_MESSAGE,"Received shutdown request"); 2446 | _write2op (wtomsg); 2447 | running = 0; 2448 | } else if ((strcmp (cmd, "TERM") == 0) && (strcmp (data->USER, AUTH_USER) == 0)) { 2449 | sprintf (wtomsg,GENERIC_MESSAGE,"Received shutdown request"); 2450 | _write2op (wtomsg); 2451 | running = 0; 2452 | } else if (strcmp (cmd, "REFR") == 0) { 2453 | refresh (); 2454 | ctl_write_line (data, "200 REFRESH command successful"); 2455 | } else if (strcmp (cmd, "REFRESH") == 0) { 2456 | refresh (); 2457 | ctl_write_line (data, "200 REFRESH command successful"); 2458 | } else { 2459 | ctl_write_line (data, "500 Huh?"); 2460 | }; 2461 | } else { 2462 | ctl_write_line (data, "501 Huh?"); 2463 | }; 2464 | 2465 | free (line); 2466 | }; 2467 | }; 2468 | 2469 | /* 2470 | # writes out buffered data, and removes the callback when DONE is true 2471 | */ 2472 | static void ftp_sock_wcb (SOCKET sock, data_tag_ptr data) { 2473 | long bytes_written; 2474 | 2475 | #ifdef debug_output 2476 | data->WRITE_BUF [data->WRITE_BUF_LENGTH] = 0; 2477 | printf ("Sending %d bytes: %s\n", data->WRITE_BUF_LENGTH, data->WRITE_BUF); 2478 | #endif 2479 | 2480 | if (data->WRITE_BUF_LENGTH) { 2481 | ebcdic2ascii (data->WRITE_BUF, data->WRITE_BUF_LENGTH); 2482 | }; 2483 | 2484 | /* just write whatever's available to be written */ 2485 | bytes_written = write_from_buf (sock, data->WRITE_BUF, data->WRITE_BUF_LENGTH); 2486 | 2487 | if (bytes_written == 0) { 2488 | printf ("error writing to control connection!\n"); 2489 | reset_data_connection (data); 2490 | remove_callbacks (sock); 2491 | 2492 | if (sock != -1) { 2493 | #ifdef debug_output 2494 | printf ("Closing down socket: %d\n", sock); 2495 | #endif 2496 | closesocket (sock); 2497 | }; 2498 | 2499 | return; 2500 | }; 2501 | 2502 | data->WRITE_BUF_LENGTH -= bytes_written; 2503 | 2504 | /* if we've written all the data, disable write callbacks */ 2505 | if (data->WRITE_BUF_LENGTH == 0) { 2506 | disable_write_callback (sock); 2507 | if (data->DONE) { 2508 | remove_callbacks (sock); 2509 | 2510 | if (sock != -1) { 2511 | #ifdef debug_output 2512 | printf ("Closing down socket: %d\n", sock); 2513 | #endif 2514 | closesocket (sock); 2515 | }; 2516 | }; 2517 | } else { /* Should never happen, but... */ 2518 | ascii2ebcdic (data->WRITE_BUF, data->WRITE_BUF_LENGTH); 2519 | }; 2520 | }; 2521 | 2522 | /* 2523 | # accept a new connection and register callbacks for it 2524 | */ 2525 | static void ftp_serv_sock_rcb (SOCKET serv_sock, data_tag_ptr data) { 2526 | SOCKET acc; 2527 | struct sockaddr client_addr; 2528 | long addr_size = sizeof (client_addr); 2529 | struct sockaddr_in * client_addr_in; 2530 | unsigned long client_ip_addr; 2531 | unsigned char * clip; 2532 | 2533 | acc = accept (serv_sock, &client_addr, &addr_size); 2534 | 2535 | if (acc == -1) { 2536 | printf ("error accepting connection: %d\n", HercGetLastError ()); 2537 | running = 0; 2538 | return; 2539 | }; 2540 | 2541 | client_addr_in = (void *)&client_addr; 2542 | client_ip_addr = client_addr_in->sin_addr.s_addr; 2543 | clip = (unsigned char *) &client_ip_addr; 2544 | 2545 | time (<); 2546 | td = localtime (<); 2547 | 2548 | sprintf (wtomsg, CONNECT_MESSAGE, clip [0], clip [1], clip [2], clip [3], 2549 | (* td).tm_hour, (* td).tm_min, (* td).tm_sec, (* td).tm_year + 1900, (* td).tm_mon + 1, (* td).tm_mday); 2550 | _write2op (wtomsg); 2551 | printf(wtomsg); 2552 | printf("\n"); 2553 | 2554 | if (sock_ip (acc) != client_ip_addr && INSECURE == 0 ) { 2555 | closesocket (acc); 2556 | 2557 | sprintf (wtomsg, REJECT_MESSAGE, clip [0], clip [1], clip [2], clip [3], 2558 | (* td).tm_hour, (* td).tm_min, (* td).tm_sec, (* td).tm_year + 1900, (* td).tm_mon + 1, (* td).tm_mday); 2559 | _write2op (wtomsg); 2560 | Sleep (1000); 2561 | return; 2562 | }; 2563 | 2564 | if (optimise < acc) optimise = acc; 2565 | 2566 | set_callbacks (acc, new_ftp_data (acc), &ftp_sock_rcb, &ftp_sock_wcb); 2567 | }; 2568 | 2569 | static long readparmlib () { 2570 | long i,j; 2571 | FILE * fh; 2572 | char * conf; 2573 | char * parm; 2574 | char line [81]; 2575 | 2576 | sprintf (wtomsg,PARMLIB_MESSAGE,PARMLIB_PRINT); 2577 | _write2op (wtomsg); 2578 | 2579 | fh = fopen (PARMLIB, "rb"); 2580 | if (fh == NULL) { 2581 | return (1); 2582 | } 2583 | 2584 | j=0; 2585 | while (fread (line, 1, 80, fh) != 0) { 2586 | j++; 2587 | /* Skip comments */ 2588 | if ( (line[0] == '#') ) { 2589 | continue ; 2590 | } 2591 | else if( strchr(line, '=') != NULL ) { 2592 | 2593 | conf = strtok(line, "= -"); 2594 | //setting = strtok(NULL, "="); 2595 | parm = strtok(NULL, "= -"); 2596 | sprintf (wtomsg,PARMLIB_READ, conf, parm); 2597 | _write2op (wtomsg); 2598 | 2599 | if( (stricmp (conf, "fast") == 0) && (stricmp (parm, "true") == 0) ) { 2600 | __libc_arch = 1; /* Enable speed optimisations within the library */ 2601 | } else if( stricmp (conf, "SRVPORT") == 0 ) { 2602 | SERVER_PORT = (short)atoi (parm); 2603 | } else if( stricmp (conf, "SRVIP") == 0 ) { 2604 | strcpy (SERVER_IP, parm); 2605 | } else if( stricmp (conf, "PASVADR") == 0 ) { 2606 | strcpy (PASVADDR, parm); 2607 | } else if( stricmp (conf, "AUTHUSER") == 0 ) { 2608 | strcpy (AUTH_USER, parm); 2609 | } else if( stricmp (conf, "INSECURE") == 0 ) { 2610 | INSECURE = atoi (parm); 2611 | if ( INSECURE !=1 && INSECURE != 0 ) { 2612 | sprintf (wtomsg,PARLMIB_ERR_I, j, PARMLIB_PRINT); 2613 | _write2op (wtomsg); 2614 | INSECURE = 0; 2615 | } 2616 | } else if( stricmp (conf, "PASVPORTS") == 0 ) { 2617 | // For setting your own passive ports PASV 2618 | 2619 | PASV_START_PORT = (short)atoi (parm); 2620 | parm = strtok(NULL, "= -"); 2621 | PASV_END_PORT = (short)atoi (parm); 2622 | sprintf(wtomsg,"FTP001I Startup - Parameter Passive port range %d to %d",PASV_START_PORT,PASV_END_PORT); 2623 | _write2op (wtomsg); 2624 | 2625 | if (PASV_START_PORT >= PASV_END_PORT) { 2626 | sprintf (wtomsg,PARLMIB_ERR_P, j, PARMLIB_PRINT,PASV_START_PORT,PASV_END_PORT); 2627 | _write2op (wtomsg); 2628 | PASV_START_PORT = 0; 2629 | } else if(PASV_END_PORT - PASV_START_PORT < 10) { 2630 | sprintf (wtomsg,PARLMIB_ERR_S, j, PARMLIB_PRINT); 2631 | _write2op (wtomsg); 2632 | PASV_START_PORT = 0; 2633 | 2634 | } else if(PASV_START_PORT < 1024 ) { 2635 | sprintf (wtomsg,PARLMIB_ERR_E, j, PARMLIB_PRINT); 2636 | _write2op (wtomsg); 2637 | PASV_START_PORT = 0; 2638 | } 2639 | } else { 2640 | /* Error on line X in config file */ 2641 | sprintf (wtomsg,PARLMIB_ERROR_L, j, PARMLIB_PRINT); 2642 | _write2op (wtomsg); 2643 | } 2644 | 2645 | 2646 | } else if ( strchr(line, ',') != NULL) { 2647 | /* No = means its not a parm, likely disk */ 2648 | continue; 2649 | } else { 2650 | /* You have an error in your config file */ 2651 | sprintf (wtomsg,PARLMIB_ERROR_L, j, PARMLIB_PRINT); 2652 | _write2op (wtomsg); 2653 | } 2654 | 2655 | } 2656 | 2657 | fclose (fh); 2658 | 2659 | return (0); 2660 | } 2661 | 2662 | void main (int argc, char ** argv) { 2663 | long serv_ip; 2664 | SOCKET serv_sock; 2665 | long i; 2666 | long j; 2667 | char * argument; 2668 | char * option; 2669 | size_t optind; 2670 | 2671 | data_tag_ptr data; 2672 | cb_data_ptr ohead; 2673 | p_root r; 2674 | int dircount = 0; 2675 | 2676 | sprintf (wtomsg, INIT_MESSAGE, VERSION, argc-1); 2677 | _write2op (wtomsg); 2678 | 2679 | PARMLIB = (char *)malloc (MAXDSN); 2680 | PARMLIB_PRINT = (char *)malloc (MAXDSN); 2681 | AUTH_USER = (char *)malloc (9); 2682 | 2683 | // Setup the defaults 2684 | 2685 | sprintf(SERVER_IP, DEFAULT_SRVIP); 2686 | sprintf(PASVADDR, DEFAULT_PASVADDR); 2687 | sprintf(AUTH_USER, '\0'); 2688 | SERVER_PORT = DEFAULT_SRVPORT; 2689 | PASV_START_PORT = 0; 2690 | INSECURE = 0; 2691 | 2692 | // Read config files and change the values if appropriate 2693 | // Config file settings overide defaults 2694 | 2695 | sprintf(PARMLIB_PRINT,DEFAULT_PARMLIB); 2696 | sprintf(PARMLIB,DSNFMT,DEFAULT_PARMLIB); 2697 | 2698 | for (optind = 1; optind < argc; optind++) { 2699 | if ( strstr(argv[optind],"PARMLIB=") != NULL ) { 2700 | 2701 | argument = strtok(argv[optind], "="); 2702 | option = strtok(NULL, "="); 2703 | 2704 | if (strlen(option) > MAXDSN) { 2705 | 2706 | sprintf (wtomsg,ERROR_MESSAGES, "PARMLIB supplied longer than alloweable dataset name length. Using default."); 2707 | _write2op (wtomsg); 2708 | continue; 2709 | } 2710 | 2711 | sprintf(PARMLIB,DSNFMT,option); 2712 | sprintf(PARMLIB_PRINT,option); 2713 | 2714 | } 2715 | 2716 | } 2717 | 2718 | 2719 | i = readparmlib(); 2720 | if (i != 0) { 2721 | printf ("Couldn't initialise\n"); 2722 | sprintf (wtomsg, PARLMIB_ERROR, PARMLIB); 2723 | _write2op (wtomsg); 2724 | return; 2725 | }; 2726 | 2727 | // Done reading config file time to parse arguments 2728 | // Arguments overide the defaults and config file settings 2729 | 2730 | for (optind = 1; optind < argc; optind++) { 2731 | if ( strstr(argv[optind],"PARMLIB=") == NULL ) { 2732 | argument = strtok(argv[optind], "="); 2733 | option = strtok(NULL, "="); 2734 | if( (stricmp (argument, "fast") == 0) && (stricmp (option, "true") == 0) ) { 2735 | __libc_arch = 1; /* Enable speed optimisations within the library */ 2736 | } else if( stricmp (argument, "SRVPORT") == 0 ) { 2737 | sprintf (wtomsg,ARG_MESSAGES_I, optind, argument, option, SERVER_PORT); 2738 | _write2op (wtomsg); 2739 | SERVER_PORT = (short)atoi (option); 2740 | } else if( stricmp (argument, "SRVIP") == 0 ) { 2741 | sprintf (wtomsg,ARG_MESSAGES_S, optind, argument, option, SERVER_IP); 2742 | _write2op (wtomsg); 2743 | strcpy (SERVER_IP, option); 2744 | } else if( stricmp (argument, "PASVADR") == 0 ) { 2745 | sprintf (wtomsg,ARG_MESSAGES_S, optind, argument, option, PASVADDR); 2746 | _write2op (wtomsg); 2747 | strcpy (PASVADDR, option); 2748 | } else if( stricmp (argument, "AUTHUSR") == 0 ) { 2749 | // Technically AUTHUSER but parms can only be 7 chars apparently 2750 | sprintf (wtomsg,ARG_MESSAGES_S, optind, argument, option, AUTH_USER); 2751 | _write2op (wtomsg); 2752 | strcpy (AUTH_USER, option); 2753 | } else if( stricmp (argument, "DD") == 0 ) { 2754 | // DD names that FTP can access 2755 | // Can be used for internal reader etc 2756 | if (added_ddc < added_ddl) { 2757 | sprintf (wtomsg,DDNAME_MESSAGES, optind, option); 2758 | _write2op (wtomsg); 2759 | strcpy (added_dds [added_ddc], option); 2760 | strupr (added_dds [added_ddc]); 2761 | added_ddc++; 2762 | } else { 2763 | sprintf (wtomsg,GENERIC_WARNING, "Max DD names reached"); 2764 | _write2op (wtomsg); 2765 | } 2766 | } else { 2767 | /* Unrocignized Argument */ 2768 | sprintf (wtomsg,ARG_ERROR, optind, argument, option); 2769 | _write2op (wtomsg); 2770 | } 2771 | } 2772 | } // End of argument for loop 2773 | 2774 | #ifdef debug_output 2775 | printf ("Init...\n"); 2776 | #endif 2777 | i = init (); 2778 | 2779 | #ifdef debug_output 2780 | printf ("Directory Read...\n"); 2781 | #endif 2782 | 2783 | i += dirsupport (); 2784 | 2785 | #ifdef debug_output 2786 | printf ("VTOC Reads... (%d devices)\n", numdevs); 2787 | #endif 2788 | 2789 | if (i == 0) { 2790 | r = (p_root)malloc (sizeof (t_root)); 2791 | if (r == NULL) { 2792 | printf ("Out of memory starting, vtoc stage\n"); 2793 | return; 2794 | } 2795 | r->dircount = 0; 2796 | r->next = NULL; 2797 | j = 0; 2798 | 2799 | sprintf (wtomsg, "FTP002I Startup - Reading %d DASD", numdevs); 2800 | _write2op (wtomsg); 2801 | while (j < numdevs) { 2802 | 2803 | sprintf (wtomsg, DEVICE_MESSAGES,devices [j], units [j]); 2804 | _write2op (wtomsg); 2805 | 2806 | i += readvtoc (devices [j], units [j], r); 2807 | j++; 2808 | }; 2809 | root = r; 2810 | }; 2811 | 2812 | if (i != 0) { 2813 | printf ("Couldn't initialise\n"); 2814 | sprintf (wtomsg, PARLMIB_ERROR, PARMLIB_PRINT); 2815 | _write2op (wtomsg); 2816 | return; 2817 | }; 2818 | 2819 | //#ifdef debug_output 2820 | r = root; 2821 | while (r) { 2822 | dircount += r->dircount; 2823 | r = r->next; 2824 | } 2825 | sprintf (wtomsg, DATASET_MESSAGE, dircount); 2826 | _write2op (wtomsg); 2827 | //#endif 2828 | 2829 | if (stricmp (SERVER_IP, "any") != 0) { 2830 | inet_aton (SERVER_IP, (void *)&serv_ip); 2831 | } else { 2832 | serv_ip = htonl (INADDR_ANY); 2833 | }; 2834 | 2835 | serv_sock = listen_sock (SERVER_PORT, serv_ip); 2836 | if (serv_sock < 0) { 2837 | printf ("Can't Listen error %d, is FTP already running on Port %d?\n", errno, SERVER_PORT); 2838 | sprintf (wtomsg, LISTEN_ERROR, SERVER_PORT, errno); 2839 | _write2op (wtomsg); 2840 | 2841 | } else { 2842 | #ifdef debug_output 2843 | printf ("Listening on Socket: %d, Port: %d\n", serv_sock, SERVER_PORT); 2844 | #endif 2845 | 2846 | sprintf (wtomsg, SOCKET_MESSAGE, SERVER_PORT); 2847 | _write2op (wtomsg); 2848 | 2849 | set_callbacks (serv_sock, NULL, &ftp_serv_sock_rcb, NULL); 2850 | 2851 | run_callbacks (); 2852 | 2853 | while (head) { 2854 | ohead = head; 2855 | data = head->FUNC_DATA; 2856 | 2857 | if (data == NULL) { 2858 | if (ohead->FH != -1) { 2859 | #ifdef debug_output 2860 | printf ("Closing down socket: %d\n", ohead->FH); 2861 | #endif 2862 | closesocket (ohead->FH); 2863 | }; 2864 | head = head->next; 2865 | free (ohead); 2866 | }; 2867 | 2868 | while (data) { 2869 | remove_callbacks_f (data->FILE); 2870 | 2871 | if (ohead != head) break; /* Was completely removed! */ 2872 | 2873 | if (data->FILE) { 2874 | #ifdef debug_output 2875 | printf ("Closing down file: %d\n", (int)data->FILE); 2876 | #endif 2877 | fclose (data->FILE); 2878 | }; 2879 | data->FILE = NULL; 2880 | 2881 | remove_callbacks (data->DATA_SOCK); 2882 | 2883 | if (ohead != head) break; /* Was completely removed! */ 2884 | 2885 | if (data->DATA_SOCK != -1) { 2886 | #ifdef debug_output 2887 | printf ("Closing down socket: %d\n", data->DATA_SOCK); 2888 | #endif 2889 | closesocket (data->DATA_SOCK); 2890 | }; 2891 | data->DATA_SOCK = -1; 2892 | 2893 | remove_callbacks (data->DATA_LISTEN_SOCK); 2894 | 2895 | if (ohead != head) break; /* Was completely removed! */ 2896 | 2897 | if (data->DATA_LISTEN_SOCK != -1) { 2898 | #ifdef debug_output 2899 | printf ("Closing down socket: %d\n", data->DATA_LISTEN_SOCK); 2900 | #endif 2901 | closesocket (data->DATA_LISTEN_SOCK); 2902 | } 2903 | data->DATA_LISTEN_SOCK = -1; 2904 | 2905 | if (data->SOCK != -1) { 2906 | #ifdef debug_output 2907 | printf ("Closing down socket: %d\n", data->SOCK); 2908 | #endif 2909 | closesocket (data->SOCK); 2910 | } 2911 | remove_callbacks (data->SOCK); 2912 | 2913 | data->SOCK = -1; // This isn't really needed! 2914 | if (ohead == head) 2915 | head = head->next; // Something bad happened, ignore this entry. 2916 | 2917 | break; 2918 | }; 2919 | }; 2920 | }; 2921 | //#ifdef debug_output 2922 | 2923 | sprintf (wtomsg, SHUTDOWN_MESSAGE); 2924 | _write2op (wtomsg); 2925 | //#endif 2926 | }; 2927 | -------------------------------------------------------------------------------- /source/c/mvsdirs.h: -------------------------------------------------------------------------------- 1 | 2 | // Copyright (c)2003, 2009 Jason Paul Winter, All Rights Reserved. 3 | 4 | 5 | #define maxdevs 200 6 | 7 | static long numdevs = 0; 8 | static char devices [maxdevs][7]; 9 | static char units [maxdevs][7]; 10 | 11 | /* 12 | SMFID/SYSNAME 13 | */ 14 | static void _sysname(char * smfid) 15 | { 16 | void ** psa; // PSA => 0 / 0x00 17 | void ** cvt; // FLCCVT => 16 / 0x10 18 | void ** smca; // CVTSMCA => 196 / 0xC4 19 | void ** csd; // CVT+660 20 | void ** smcasid; // SMCASID => 16 / 0x10 21 | 22 | psa = 0; 23 | cvt = psa[4]; // 16 24 | smca = cvt[49]; // 196 25 | smcasid = smca + 4; // 16 26 | memcpy(smfid,smcasid,4); 27 | } 28 | 29 | 30 | /* This is a non UCB access version of obtaining DASD system info: */ 31 | static long dirsupport () { 32 | long i,j; 33 | FILE * fh; 34 | char * volser; 35 | char * unit; 36 | char * comment; 37 | //char * unit; 38 | char line [81]; 39 | 40 | fh = fopen (PARMLIB, "rb"); 41 | if (fh == NULL) { 42 | return (1); 43 | } 44 | 45 | j=0; 46 | while (fread (line, 1, 80, fh) != 0) { 47 | j++; 48 | 49 | if ( (line[0] == '#') || ( strchr(line, '=') != NULL) || strchr(line, ',') == NULL ) { 50 | /* If it starts with a comment or has an equal sign skip it */ 51 | /* or its missing a comma we also skip it and readparms has already printed */ 52 | /* the error. */ 53 | continue ; 54 | } 55 | 56 | volser = strtok(line, ", "); 57 | unit = strtok(NULL, ", "); 58 | comment = strtok(NULL, ", "); 59 | 60 | strncpy (devices [numdevs], volser, 6); 61 | i = 6; 62 | do { 63 | devices [numdevs][i--] = 0; 64 | } while (devices [numdevs][i] == ' '); 65 | 66 | strncpy (units [numdevs], unit, 8); 67 | i = 6; 68 | do { 69 | units [numdevs][i--] = 0; 70 | } while (units [numdevs][i] == ' '); 71 | 72 | if (++numdevs == maxdevs) break; 73 | } 74 | 75 | fclose (fh); 76 | 77 | return (0); 78 | } 79 | 80 | static char * disktype [] = { 81 | "UNKN", // 0 82 | "2311", // 1 83 | "2301", // 2 84 | "2303", // 3 85 | "2302", // 4 - could be "9345" if (c [17] != 0) 86 | "2321", // 5 87 | "2305-1", // 6 88 | "2305-2", // 7 89 | "2314", // 8 90 | "3330", // 9 91 | "3340", // A 92 | "3350", // B 93 | "3375", // C 94 | "3330-1", // D 95 | "3380", // E 96 | "3390" // F 97 | }; 98 | 99 | static void adddev (char * serial, char * model) { 100 | long i; 101 | 102 | if (numdevs == maxdevs) return; 103 | 104 | strncpy (devices [numdevs], serial, 6); 105 | i = 6; 106 | do { 107 | devices [numdevs][i--] = 0; 108 | } while ((i) && (devices [numdevs][i] == ' ')); 109 | 110 | strcpy (units [numdevs], model); 111 | 112 | numdevs++; 113 | } 114 | 115 | //extern int __libc_oarch; // Moved to ftpd.c 116 | 117 | #define UCBSCAN 18 /* Internal JCC library function */ 118 | long LOWLEVIO (long func, long p1, long p2, long * savearea); 119 | 120 | typedef struct ucbscan_tag { 121 | unsigned char parm [116]; /* Service parameters */ 122 | unsigned char work [100]; /* Restart token */ 123 | unsigned char ucbc [48]; /* Copy of UCB */ 124 | } t_ucbscan; 125 | 126 | #define ucbinit(scan) memset ((scan)->work, 0, 100) 127 | 128 | /* The following macro is not used here, but will return the device number: */ 129 | #define ucbnchr(dest, scan) {memcpy (dest, &((scan)->parm [36]), 4); dest [4] = 0;} 130 | 131 | int ucbscan (t_ucbscan * scan) { 132 | long savearea [10]; /* For Registers 2..11 */ 133 | 134 | memset (scan->parm, 0, 116); 135 | 136 | scan->parm [0] = 1; /* Version=1 */ 137 | *((unsigned char **)&(scan->parm [4])) = &(scan->ucbc [0]); 138 | *((unsigned char **)&(scan->parm [12])) = &(scan->work [0]); 139 | scan->parm [96] = 32; /* DASD */ 140 | scan->parm [97] = 128; /* NONBASE=NO */ 141 | scan->parm [98] = 16; /* DEVNCHAR=(get) */ 142 | 143 | return (LOWLEVIO (UCBSCAN, (long)scan, 2, savearea)); 144 | } 145 | 146 | typedef struct dirs_tag * dirs_tag_ptr; 147 | typedef struct dirs_tag { 148 | char name [45]; 149 | long date; 150 | long org; /* 1 = PS, 2 = PO */ 151 | } dirs; 152 | 153 | #define maxdirs 100 154 | 155 | typedef struct _root * p_root; 156 | typedef struct _root { 157 | long dircount; 158 | dirs directories [maxdirs]; 159 | p_root next; 160 | } t_root; 161 | 162 | static p_root root = NULL; // Nothing in the list 163 | 164 | //** Dir-Util function 165 | static int getorg (char * name) { // Not found? rc=0 166 | p_root r = root; // Start point 167 | int i; 168 | while (r) { 169 | i = 0; 170 | while (i < r->dircount) { 171 | if (strcasecmp (r->directories [i].name, name) == 0) 172 | return (r->directories [i].org); 173 | i++; 174 | } 175 | r = r->next; 176 | } 177 | return (0); 178 | } 179 | 180 | static long _ftp_i = 1; 181 | static char _ftp_ly [250]; // 1970-2220 182 | 183 | static long leapdays (long year) { 184 | long i; 185 | long j; 186 | long k = year - 1970; 187 | 188 | if (_ftp_i) { // init? 189 | _ftp_i = 0; // not again! 190 | i = 0; 191 | while (i < 250) { // 250 years into the future! 192 | //if (1970 + i % 4 == 0) 193 | if (((1970 + i) & 3) == 0) 194 | _ftp_ly [i] = 1; // Is leap 195 | else 196 | _ftp_ly [i] = 0; // Not leap 197 | i++; 198 | } 199 | } 200 | 201 | i = 0; 202 | j = 0; 203 | while (j != k) { 204 | if (_ftp_ly [j]) { 205 | //if ((j + 1970) % 4 == 0) { 206 | i = i + 366; 207 | } else { 208 | i = i + 365; 209 | } 210 | j++; 211 | } 212 | 213 | return (i); 214 | } 215 | 216 | static long readvtoc (char * device, char * unit, p_root rt) { // r is the newroot 217 | long i; 218 | char * s; 219 | FILE * fh; 220 | char str [40]; 221 | unsigned char b [96]; 222 | char * t; // trim! 223 | 224 | 225 | p_root c = rt; // Current 226 | while (c->next) 227 | c = c->next; // Find the correct place to add new entries 228 | if (c->dircount == maxdirs) return (0); // Out of memory previously 229 | 230 | sprintf (str, "rb,vtoc,unit=%s,volser=%s", unit, device); 231 | fh = fopen ("", str); // Open VTOC 232 | if (fh == NULL) return (1); // Nothing on this volume??? 233 | 234 | while (fread (b, 1, 96, fh) == 96) { 235 | if ((b [44] != '1') || 236 | ((b [82] != 0x40) && (b [82] != 0x02))) 237 | continue; // Skip if it's not a DSN, PS or PO 238 | t = c->directories [c->dircount].name; 239 | memcpy (t, b, 44); 240 | i = 44; 241 | do { 242 | t [i--] = 0; 243 | } while ((i) && (t [i] == ' ')); 244 | 245 | if (b [82] == 0x40) 246 | c->directories [c->dircount].org = 1; 247 | else 248 | c->directories [c->dircount].org = 2; 249 | 250 | i = b [75]; /* Calc year */ 251 | if (i >= 70) { 252 | i = i + 1900; 253 | } else { 254 | i = i + 2000; 255 | } 256 | i = leapdays (i); 257 | i += (((b [76] << 8) + b [77]) - 1); /* Add days */ 258 | c->directories [c->dircount].date = i * 24 * 60 * 60; /* time=seconds past 1970 */ 259 | 260 | (c->dircount)++; 261 | 262 | if (c->dircount == maxdirs) { // break; 263 | c->next = (p_root)malloc (sizeof (t_root)); 264 | if (c->next == NULL) 265 | break; 266 | c = c->next; 267 | c->dircount = 0; 268 | c->next = NULL; 269 | } 270 | } 271 | fclose (fh); 272 | return (0); 273 | } 274 | 275 | #define maxdirent 3000 276 | static long pdsecount; 277 | static char pdsename [maxdirent][9]; 278 | 279 | #define endmark "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" 280 | #define SKIP_MASK ((int) 0x1F) 281 | #define ALIAS_MASK ((int) 0x80) 282 | 283 | long readdir (char * pds, unsigned int acee) { 284 | long i; 285 | long j; 286 | FILE * fh; 287 | char line [256]; 288 | char tstr [9]; 289 | char * a; 290 | char * name; 291 | short b; 292 | short count; 293 | short skip; 294 | long quit; 295 | int info_byte; 296 | short l; 297 | unsigned int acee_old; 298 | 299 | pdsecount = 0; 300 | 301 | if (acee > 1) { 302 | rac_ftp_auth (1); 303 | acee_old = rac_switch_user (acee); 304 | } 305 | 306 | fh = fopen (pds, "rb,klen=0,lrecl=256,blksize=256,recfm=u,force"); 307 | 308 | if (acee > 1) { 309 | rac_switch_user (acee_old); 310 | rac_ftp_auth (0); 311 | } 312 | 313 | if (fh == NULL) { 314 | printf ("Error opening directory in %s\n", pds); 315 | return (1); 316 | } 317 | 318 | fread (&l, 1, 2, fh); /* Skip U length */ 319 | 320 | quit = 0; 321 | while (fread (line, 1, 256, fh) == 256) { 322 | 323 | a = &(line [2]); 324 | b = ((short *)&(line [0])) [0]; 325 | count = 2; 326 | while (count < b) { 327 | 328 | if (memcmp (a, endmark, 8) == 0) { 329 | quit = 1; 330 | break; 331 | } 332 | 333 | name = a; 334 | a += 8; 335 | 336 | i = (((int *)a) [0]) & 0xFFFFFF00; 337 | a += 3; 338 | 339 | info_byte = (int)(*a); 340 | a++; 341 | 342 | skip = (info_byte & SKIP_MASK) * 2; 343 | 344 | strncpy (tstr, name, 8); 345 | j = 7; 346 | while (tstr [j] == ' ') j--; 347 | tstr [++j] = 0; 348 | 349 | //if ((info_byte & ALIAS_MASK) == 0) { 350 | // printf ("Member: %08X %s\n", i, tstr); 351 | //} else { 352 | // printf ("Alias: %08X %s\n", i, tstr); 353 | //} 354 | 355 | if (pdsecount == maxdirent) { 356 | quit = 1; 357 | break; 358 | } else 359 | strcpy (pdsename [pdsecount++], tstr); 360 | 361 | a += skip; 362 | 363 | count += (8 + 4 + skip); 364 | } 365 | 366 | if (quit) break; 367 | 368 | fread (&l, 1, 2, fh); /* Skip U length */ 369 | } 370 | 371 | fclose (fh); 372 | return (0); 373 | } 374 | -------------------------------------------------------------------------------- /source/hlasm/FTPAUTH.hlasm: -------------------------------------------------------------------------------- 1 | FTPAUTH TITLE 'FTP Authorization Processor' 2 | *********************************************************************** 3 | *** *** 4 | *** Program: FTPAUTH *** 5 | *** *** 6 | *** Purpose: C function to authorize or unauthorize FTPD. *** 7 | *** *** 8 | *** Usage: void rac_ftpd_auth (unsigned int state); *** 9 | *** *** 10 | *** Function: Use SVC244 to set or clear JSCBAUTH, depending on *** 11 | *** the value (0/1) of state. *** 12 | *** *** 13 | *** Updates: 2015/03/15 original implementation. *** 14 | *** *** 15 | *** Author: Juergen Winkelmann, ETH Zuerich. *** 16 | *** *** 17 | *********************************************************************** 18 | PRINT NOGEN no expansions please 19 | FTPAUTH CSECT , start of program 20 | STM R14,R12,12(R13) save registers 21 | L R2,8(,R13) \ 22 | LA R14,84(,R2) \ 23 | L R12,0(,R13) \ 24 | CL R14,4(,R12) \ 25 | BL F1-FTPAUTH+4(,R15) \ 26 | L R10,0(,R12) \ save area chaining 27 | BALR R11,R10 / and JCC prologue 28 | CNOP 0,4 / 29 | F1 DS 0H / 30 | DC F'84' / 31 | STM R12,R14,0(R2) / 32 | LR R13,R2 / 33 | LR R12,R15 establish module addressability 34 | USING FTPAUTH,R12 tell assembler of base 35 | LR R11,R1 parameter list 36 | * 37 | * set or clear? 38 | * 39 | L R3,0(,R11) state .. 40 | LTR R3,R3 .. = 0? 41 | BZ CLEAR yes -> clear 42 | * 43 | * set JSCBAUTH 44 | * 45 | BSPAUTH ON become authorized 46 | B RETURN return 47 | * 48 | * clear JSCBAUTH 49 | * 50 | CLEAR BSPAUTH OFF no longer authorized 51 | * 52 | * Return to caller 53 | * 54 | RETURN L R13,4(,R13) caller's save area pointer 55 | L R14,12(,R13) restore R14 56 | LM R1,R12,24(R13) restore registers 57 | BR R14 return to caller 58 | * 59 | * Data area 60 | * 61 | YREGS , register equates 62 | END FTPAUTH end of FTPAUTH 63 | -------------------------------------------------------------------------------- /source/hlasm/FTPDXCTL.hlasm: -------------------------------------------------------------------------------- 1 | FTPDXCTL TITLE 'Set user and group for FTPD started task' 2 | *********************************************************************** 3 | *** *** 4 | *** Program: FTPDXCTL *** 5 | *** *** 6 | *** Purpose: Wrapper for FTPD started task to run *** 7 | *** using user/group FTPD/USER instead of STC/STCGROUP *** 8 | *** *** 9 | *** Usage: Replace FTPD in PGM parameter of EXEC statement with *** 10 | *** FTPDXCTL. *** 11 | *** *** 12 | *** Function: 1. Enter supervisor state. *** 13 | *** *** 14 | *** 2. Delete current security environment. *** 15 | *** *** 16 | *** 3. Create new security environment using FTPD/USER. *** 17 | *** *** 18 | *** 4. Return to problem state. *** 19 | *** *** 20 | *** 5. Pass control to FTPD via XCTL. *** 21 | *** *** 22 | *** Updates: 2015/03/03 original implementation. *** 23 | *** *** 24 | *** Author: Juergen Winkelmann, ETH Zuerich. *** 25 | *** *** 26 | *********************************************************************** 27 | PRINT NOGEN no expansions please 28 | FTPDXCTL CSECT , start of program 29 | SAVE (14,12),,* save registers 30 | LR R12,R15 establish module addressability 31 | USING FTPDXCTL,R12 tell assembler of base 32 | LA R2,SAVEA chain .. 33 | ST R13,4(,R2) .. the .. 34 | ST R2,8(,R13) .. save .. 35 | LR R13,R2 .. areas 36 | * 37 | * Enter supervisor state 38 | * 39 | BSPAUTH ON become authorized 40 | MODESET KEY=ZERO,MODE=SUP enter supervisor state 41 | BSPAUTH OFF no longer authorized 42 | * 43 | * switch to user FTPD and group USER 44 | * 45 | RACINIT ENVIR=DELETE delete and recreate RAC environment 46 | RACINIT ENVIR=CREATE,USERID=FTPD,GROUP=USER,PASSCHK=NO 47 | * 48 | * Return to problem state 49 | * 50 | MODESET KEY=NZERO,MODE=PROB back to problem state 51 | * 52 | * Pass control to FTPD 53 | * 54 | L R13,4(,R13) caller's save area pointer 55 | L R1,24(,R13) parameter list for FTPD 56 | XCTL (2,12),EP=FTPD execute FTPD, return never 57 | * 58 | * Data area 59 | * 60 | SAVEA DS 18F save area 61 | FTPD DC X'04',C'FTPD' new user 62 | USER DC X'04',C'USER' new group 63 | YREGS , register equates 64 | END FTPDXCTL end of FTPDXCTL 65 | -------------------------------------------------------------------------------- /source/hlasm/FTPLGOUT.hlasm: -------------------------------------------------------------------------------- 1 | FTPLGOUT TITLE 'FTP User Logout Processor' 2 | *********************************************************************** 3 | *** *** 4 | *** Program: FTPLGOUT *** 5 | *** *** 6 | *** Purpose: C function to process FTP user logout. *** 7 | *** *** 8 | *** Usage: unsigned int rac_user_logout (unsigned int acee); *** 9 | *** *** 10 | *** where acee is the access control environment element *** 11 | *** address obtained from rac_user_login when the user *** 12 | *** logged in. *** 13 | *** *** 14 | *** Function: Execute RACINIT ENVIR=DELETE to log out the user *** 15 | *** identified by acee and return the RACINIT return *** 16 | *** code to the caller. *** 17 | *** *** 18 | *** Updates: 2015/03/14 original implementation. *** 19 | *** *** 20 | *** Author: Juergen Winkelmann, ETH Zuerich. *** 21 | *** *** 22 | *********************************************************************** 23 | PRINT NOGEN no expansions please 24 | FTPLGOUT CSECT , start of program 25 | STM R14,R12,12(R13) save registers 26 | L R2,8(,R13) \ 27 | LA R14,84(,R2) \ 28 | L R12,0(,R13) \ 29 | CL R14,4(,R12) \ 30 | BL F1-FTPLGOUT+4(,R15) \ 31 | L R10,0(,R12) \ save area chaining 32 | BALR R11,R10 / and JCC prologue 33 | CNOP 0,4 / 34 | F1 DS 0H / 35 | DC F'84' / 36 | STM R12,R14,0(R2) / 37 | LR R13,R2 / 38 | LR R12,R15 establish module addressability 39 | USING FTPLGOUT,R12 tell assembler of base 40 | LR R11,R1 parameter list 41 | * 42 | * enter supervisor state 43 | * 44 | BSPAUTH ON become authorized 45 | MODESET KEY=ZERO,MODE=SUP enter supervisor state 46 | BSPAUTH OFF no longer authorized 47 | * 48 | * log out 49 | * 50 | L R3,0(,R11) ACEE address 51 | RACINIT ENVIR=DELETE,ACEE=(3) logout 52 | LR R5,R15 remember return code 53 | * 54 | * return to problem state 55 | * 56 | MODESET KEY=NZERO,MODE=PROB back to problem state 57 | * 58 | * Return to caller 59 | * 60 | LR R15,R5 get RACINIT return code 61 | L R13,4(,R13) caller's save area pointer 62 | L R14,12(,R13) restore R14 63 | LM R1,R12,24(R13) restore registers 64 | BR R14 return to caller 65 | * 66 | * Data area 67 | * 68 | YREGS , register equates 69 | END FTPLGOUT end of FTPLGOUT 70 | -------------------------------------------------------------------------------- /source/hlasm/FTPLOGIN.hlasm: -------------------------------------------------------------------------------- 1 | FTPLOGIN TITLE 'FTP User Login Processor' 2 | *********************************************************************** 3 | *** *** 4 | *** Program: FTPLOGIN *** 5 | *** *** 6 | *** Purpose: C function to process user login to the FTP service. *** 7 | *** *** 8 | *** Usage: unsigned int rac_user_login (char * user, char * pass); * 9 | *** *** 10 | *** where user and pass each point to a null terminated *** 11 | *** string of up to eight characters, the username and *** 12 | *** the password of the user to be logged in. *** 13 | *** *** 14 | *** The following return values are defined: *** 15 | *** *** 16 | *** | return value *** 17 | *** -------------------+-------------- *** 18 | *** RAC not available | 1 *** 19 | *** -------------------+-------------- *** 20 | *** login successful | ACEE address *** 21 | *** -------------------+-------------- *** 22 | *** login failed | 0 *** 23 | *** *** 24 | *** Function: 1. Check for resource access control (RAC) being *** 25 | *** installed and active; always allow login if RAC *** 26 | *** is not active. *** 27 | *** *** 28 | *** 2. Convert user and pass to RAC format (eight *** 29 | *** characters preceeded by a one byte length field). *** 30 | *** *** 31 | *** 3. Authenticate user; don't allow login if *** 32 | *** authentication fails. *** 33 | *** *** 34 | *** 4. Check for authorization to use FTP: If the user *** 35 | *** has read access to resource FTPAUTH in class *** 36 | *** FACILITY, allow login -- otherwise fail. *** 37 | *** *** 38 | *** Updates: 2015/03/08 original implementation. *** 39 | *** 2015/03/14 return ACEE address. *** 40 | *** *** 41 | *** Author: Juergen Winkelmann, ETH Zuerich. *** 42 | *** *** 43 | *********************************************************************** 44 | PRINT NOGEN no expansions please 45 | FTPLOGIN CSECT , start of program 46 | STM R14,R12,12(R13) save registers 47 | L R2,8(,R13) \ 48 | LA R14,96(,R2) \ 49 | L R12,0(,R13) \ 50 | CL R14,4(,R12) \ 51 | BL F1-FTPLOGIN+4(,R15) \ 52 | L R10,0(,R12) \ save area chaining 53 | BALR R11,R10 / and JCC prologue 54 | CNOP 0,4 / 55 | F1 DS 0H / 56 | DC F'96' / 57 | STM R12,R14,0(R2) / 58 | LR R13,R2 / 59 | LR R12,R15 establish module addressability 60 | USING FTPLOGIN,R12 tell assembler of base 61 | LR R11,R1 parameter list 62 | * 63 | * verify RAC availability 64 | * 65 | LA R7,1 return code if RAC unavailable 66 | L R1,CVTPTR get CVT address 67 | ICM R1,B'1111',CVTSAF(R1) SAFV defined ? 68 | BZ LOGNOK no RAC, allow login 69 | USING SAFV,R1 addressability of SAFV 70 | CLC SAFVIDEN(4),SAFVID SAFV initialized ? 71 | BNE LOGNOK no RAC, allow login 72 | DROP R1 SAFV no longer needed 73 | * 74 | * convert C null terminated strings to RAC format 75 | * 76 | L R3,0(,R11) username address 77 | TRT 0(9,R3),EOS find end of string 78 | CR R1,R3 null string? 79 | BE LOGNFAIL yes -> fail 80 | SR R1,R3 length of string 81 | STC R1,USER store length in RAC username field 82 | BCTR R1,0 decrement for execute 83 | EX R1,MOVEUSER get username 84 | L R3,4(,R11) password address 85 | TRT 0(9,R3),EOS find end of string 86 | CR R1,R3 null string? 87 | BE LOGNFAIL yes -> fail 88 | SR R1,R3 length of string 89 | STC R1,PASS store length in RAC password field 90 | BCTR R1,0 decrement for execute 91 | EX R1,MOVEPASS get password 92 | OC USER+1(8),UPPER translate username to upper case 93 | OC PASS+1(8),UPPER translate password to upper case 94 | * 95 | * enter supervisor state 96 | * 97 | BSPAUTH ON become authorized 98 | MODESET KEY=ZERO,MODE=SUP enter supervisor state 99 | BSPAUTH OFF no longer authorized 100 | * 101 | * authenticate user 102 | * 103 | RACINIT ENVIR=CREATE,USERID=USER,PASSWRD=PASS,ACEE=ACEE 104 | LTR R5,R15 authentication successful? 105 | BNZ PROB no -> return to problem state 106 | * 107 | * check authorization 108 | * 109 | LA R6,0 get PSA address 110 | USING PSA,R6 tell assembler 111 | L R6,PSAAOLD get ASCB address 112 | USING ASCB,R6 tell assembler 113 | L R6,ASCBASXB get ASXB address 114 | USING ASXB,R6 tell assembler 115 | L R7,ASXBSENV remember ACEE of current user 116 | MVC ASXBSENV(4),ACEE use ACEE of newly authenticated user 117 | RACHECK ENTITY=FTPAUTH,CLASS='FACILITY',ATTR=READ check access 118 | LR R5,R15 remember return code 119 | ST R7,ASXBSENV restore ACEE of current user 120 | DROP R6 ASXB no longer needed 121 | L R7,ACEE ACEE of newly logged in user 122 | LTR R5,R5 authorization ok? 123 | BZ PROB yes -> skip logout 124 | RACINIT ENVIR=DELETE,ACEE=ACEE no -> logout 125 | * 126 | * return to problem state 127 | * 128 | PROB MODESET KEY=NZERO,MODE=PROB back to problem state 129 | LTR R5,R5 authentication & authorization ok? 130 | BNZ LOGNFAIL no -> signal failure 131 | LOGNOK LR R15,R7 get return code 132 | B RETURN return to caller 133 | LOGNFAIL LA R15,0 return (0); 134 | * 135 | * Return to caller 136 | * 137 | RETURN L R13,4(,R13) caller's save area pointer 138 | L R14,12(,R13) restore R14 139 | LM R1,R12,24(R13) restore registers 140 | BR R14 return to caller 141 | * 142 | * Executed instructions 143 | * 144 | MOVEUSER MVC USER+1(0),0(R3) get username 145 | MOVEPASS MVC PASS+1(0),0(R3) get password 146 | * 147 | * Data area 148 | * 149 | ACEE DS F ACEE for authentication 150 | USER DS CL9 username 151 | PASS DS CL9 password 152 | UPPER DC C' ' for uppercase translation 153 | EOS DC X'01',255X'00' table to find end of string delimiter 154 | FTPAUTH DC CL39'FTPAUTH' facility name to authorize 155 | SAFVID DC CL4'SAFV' SAFV eye catcher 156 | YREGS , register equates 157 | CVT DSECT=YES map CVT 158 | IHAPSA , map PSA 159 | IHAASCB , map ASCB 160 | IHAASXB , map ASXB 161 | CVTSAF EQU 248 CVTSAF doesn't exist but is a reserved field in 3.8J 162 | ICHSAFV DSECT=YES map SAFV 163 | END FTPLOGIN end of FTPLOGIN 164 | -------------------------------------------------------------------------------- /source/hlasm/FTPSU.hlasm: -------------------------------------------------------------------------------- 1 | FTPSU TITLE 'FTP Switch User Processor' 2 | *********************************************************************** 3 | *** *** 4 | *** Program: FTPSU *** 5 | *** *** 6 | *** Purpose: C function to switch user *** 7 | *** *** 8 | *** Usage: unsigned int rac_switch_user (unsigned int acee); *** 9 | *** *** 10 | *** where acee is the access control environment element *** 11 | *** address obtained from rac_user_login when the user *** 12 | *** logged in. *** 13 | *** *** 14 | *** Function: Replace the contents of ASXBSENV with acee and return *** 15 | *** the previous ASXBENV contents to the caller. *** 16 | *** *** 17 | *** Updates: 2015/03/15 original implementation. *** 18 | *** *** 19 | *** Author: Juergen Winkelmann, ETH Zuerich. *** 20 | *** *** 21 | *********************************************************************** 22 | PRINT NOGEN no expansions please 23 | FTPSU CSECT , start of program 24 | STM R14,R12,12(R13) save registers 25 | L R2,8(,R13) \ 26 | LA R14,96(,R2) \ 27 | L R12,0(,R13) \ 28 | CL R14,4(,R12) \ 29 | BL F1-FTPSU+4(,R15) \ 30 | L R10,0(,R12) \ save area chaining 31 | BALR R11,R10 / and JCC prologue 32 | CNOP 0,4 / 33 | F1 DS 0H / 34 | DC F'96' / 35 | STM R12,R14,0(R2) / 36 | LR R13,R2 / 37 | LR R12,R15 establish module addressability 38 | USING FTPSU,R12 tell assembler of base 39 | LR R11,R1 parameter list 40 | * 41 | * enter supervisor state 42 | * 43 | MODESET KEY=ZERO,MODE=SUP enter supervisor state 44 | * 45 | * switch user 46 | * 47 | LA R6,0 get PSA address 48 | USING PSA,R6 tell assembler 49 | L R6,PSAAOLD get ASCB address 50 | USING ASCB,R6 tell assembler 51 | L R6,ASCBASXB get ASXB address 52 | USING ASXB,R6 tell assembler 53 | L R7,ASXBSENV remember ACEE address of current user 54 | L R3,0(,R11) ACEE address of new user 55 | ST R3,ASXBSENV switch to new user 56 | DROP R6 ASXB no longer needed 57 | * 58 | * return to problem state 59 | * 60 | MODESET KEY=NZERO,MODE=PROB back to problem state 61 | LR R15,R7 return ACEE adress of previous user 62 | * 63 | * Return to caller 64 | * 65 | RETURN L R13,4(,R13) caller's save area pointer 66 | L R14,12(,R13) restore R14 67 | LM R1,R12,24(R13) restore registers 68 | BR R14 return to caller 69 | * 70 | * Data area 71 | * 72 | YREGS , register equates 73 | IHAPSA , map PSA 74 | IHAASCB , map ASCB 75 | IHAASXB , map ASXB 76 | END FTPSU end of FTPSU 77 | --------------------------------------------------------------------------------