Back to index

nordugrid-arc-nox  1.1.0~rc6
FTPControl.cpp
Go to the documentation of this file.
00001 // -*- indent-tabs-mode: nil -*-
00002 
00003 #ifdef HAVE_CONFIG_H
00004 #include <config.h>
00005 #endif
00006 
00007 #include <arc/Logger.h>
00008 #include <arc/Thread.h>
00009 #include <arc/globusutils/GlobusErrorUtils.h>
00010 #include <arc/globusutils/GSSCredential.h>
00011 
00012 #include "FTPControl.h"
00013 
00014 namespace Arc {
00015 
00016   class FTPControl::CBArg {
00017    private:
00018     unsigned int counter_;
00019     ~CBArg(void);
00020    public:
00021     Arc::SimpleCondition cond;
00022     std::string response;
00023     bool responseok;
00024     bool data;
00025     bool ctrl;
00026     bool close;
00027     CBArg(void);
00028     CBArg* claim(void);
00029     bool release(void);
00030   };
00031 
00032   FTPControl::CBArg::CBArg(void):counter_(1) {
00033   }
00034 
00035   FTPControl::CBArg::~CBArg(void) {
00036   }
00037 
00038   FTPControl::CBArg* FTPControl::CBArg::claim(void) {
00039     cond.lock();
00040     ++counter_;
00041     cond.unlock();
00042     return this;
00043   }
00044 
00045   bool FTPControl::CBArg::release(void) {
00046     cond.lock();
00047     --counter_;
00048     bool done = (counter_ <= 0);
00049     cond.unlock();
00050     // This looks unsafe, but is alright for how it is used
00051     if(done) delete this;
00052     return done;
00053   }
00054 
00055   static void CloseCallback(void *arg, globus_ftp_control_handle_t*,
00056                             globus_object_t *error,
00057                             globus_ftp_control_response_t *response) {
00058     FTPControl::CBArg *cb = (FTPControl::CBArg*)arg;
00059     // TODO: handle error - if ever can happen here
00060     cb->close = true;
00061     cb->cond.signal();
00062     cb->release();
00063   }
00064 
00065   static void ControlCallback(void *arg, globus_ftp_control_handle_t*,
00066                               globus_object_t *error,
00067                               globus_ftp_control_response_t *response) {
00068     FTPControl::CBArg *cb = (FTPControl::CBArg*)arg;
00069     if (error != GLOBUS_SUCCESS) {
00070       cb->response = Arc::globus_object_to_string(error);
00071       cb->responseok = false;
00072     }
00073     if (response && response->response_buffer) {
00074       int len = response->response_length;
00075       while (len > 0 && (response->response_buffer[len - 1] == '\r' ||
00076                          response->response_buffer[len - 1] == '\n' ||
00077                          response->response_buffer[len - 1] == '\0'))
00078         len--;
00079       cb->response.assign((const char*)response->response_buffer, len);
00080       switch (response->response_class) {
00081       case GLOBUS_FTP_POSITIVE_PRELIMINARY_REPLY:
00082       case GLOBUS_FTP_POSITIVE_COMPLETION_REPLY:
00083       case GLOBUS_FTP_POSITIVE_INTERMEDIATE_REPLY:
00084         cb->responseok = true;
00085         break;
00086 
00087       default:
00088         cb->responseok = false;
00089         break;
00090       }
00091     }
00092     cb->ctrl = true;
00093     cb->cond.signal();
00094   }
00095 
00096   static void ConnectCallback(void *arg, globus_ftp_control_handle_t*,
00097                               globus_object_t *error,
00098                               globus_ftp_control_response_t *response) {
00099     FTPControl::CBArg *cb = (FTPControl::CBArg*)arg;
00100     ControlCallback(arg,NULL,error,response);
00101     cb->release();
00102   }
00103 
00104   static void DataConnectCallback(void *arg, globus_ftp_control_handle_t*,
00105                               unsigned int, globus_bool_t, globus_object_t*) {
00106     FTPControl::CBArg *cb = (FTPControl::CBArg*)arg;
00107     cb->data = true;
00108     cb->cond.signal();
00109   }
00110 
00111   static void ReadWriteCallback(void *arg, globus_ftp_control_handle_t*,
00112                                 globus_object_t*, globus_byte_t*, globus_size_t,
00113                                 globus_off_t, globus_bool_t) {
00114     FTPControl::CBArg *cb = (FTPControl::CBArg*)arg;
00115     cb->data = true;
00116     cb->cond.signal();
00117   }
00118 
00119   static int pwck(char*, int, int) {
00120     return -1;
00121   }
00122 
00123   Logger FTPControl::logger(Logger::getRootLogger(), "FTPControl");
00124 
00125   FTPControl::FTPControl() {
00126     connected = false;
00127     cb = new CBArg;
00128     globus_module_activate(GLOBUS_FTP_CONTROL_MODULE);
00129   }
00130 
00131   FTPControl::~FTPControl() {
00132     if(connected) Disconnect(10);
00133     globus_module_deactivate(GLOBUS_FTP_CONTROL_MODULE);
00134     cb->release();
00135   }
00136 
00137   bool FTPControl::Connect(const URL& url,
00138                            const std::string& proxyPath,
00139                            const std::string& certificatePath,
00140                            const std::string& keyPath,
00141                            int timeout) {
00142 
00143     bool timedin;
00144     GlobusResult result;
00145 
00146     result = globus_ftp_control_handle_init(&control_handle);
00147     if (!result) {
00148       logger.msg(VERBOSE, "Connect: Failed to init handle: %s", result.str());
00149       return false;
00150     }
00151 
00152     cb->ctrl = false;
00153     result = globus_ftp_control_connect(&control_handle,
00154                                         const_cast<char*>(url.Host().c_str()),
00155                                         url.Port(), &ConnectCallback,
00156                                         cb->claim());
00157     if (!result) {
00158       cb->release();
00159       logger.msg(VERBOSE, "Connect: Failed to connect: %s", result.str());
00160       return false;
00161     }
00162     while (!cb->ctrl) {
00163       timedin = cb->cond.wait(timeout * 1000);
00164       if (!timedin) {
00165         logger.msg(VERBOSE, "Connect: Connecting timed out after %d ms",
00166                    timeout * 1000);
00167         return false;
00168       }
00169     }
00170     if (!cb->responseok) {
00171       logger.msg(VERBOSE, "Connect: Failed to connect: %s", cb->response);
00172       return false;
00173     }
00174     connected = true;
00175 
00176     GSSCredential handle(proxyPath, certificatePath, keyPath);
00177 
00178     globus_ftp_control_auth_info_t auth;
00179     result = globus_ftp_control_auth_info_init(&auth, handle, GLOBUS_TRUE,
00180                                                const_cast<char*>("ftp"),
00181                                                const_cast<char*>("user@"),
00182                                                GLOBUS_NULL, GLOBUS_NULL);
00183     if (!result) {
00184       logger.msg(VERBOSE, "Connect: Failed to init auth info handle: %s",
00185                  result.str());
00186       return false;
00187     }
00188 
00189     cb->ctrl = false;
00190     result = globus_ftp_control_authenticate(&control_handle, &auth,
00191                                              GLOBUS_TRUE,
00192                                              &ControlCallback, cb);
00193     if (!result) {
00194       logger.msg(VERBOSE, "Connect: Failed authentication: %s", result.str());
00195       return false;
00196     }
00197     while (!cb->ctrl) {
00198       timedin = cb->cond.wait(timeout * 1000);
00199       if (!timedin) {
00200         logger.msg(VERBOSE, "Connect: Authentication timed out after %d ms",
00201                    timeout * 1000);
00202         return false;
00203       }
00204     }
00205     if (!cb->responseok) {
00206       logger.msg(VERBOSE, "Connect: Failed authentication: %s", cb->response);
00207       return false;
00208     }
00209 
00210     return true;
00211 
00212   } // end Connect
00213 
00214   bool FTPControl::SendCommand(const std::string& cmd, int timeout) {
00215 
00216     bool timedin;
00217     GlobusResult result;
00218 
00219     cb->ctrl = false;
00220     result = globus_ftp_control_send_command(&control_handle, cmd.c_str(),
00221                                              &ControlCallback, cb);
00222     if (!result) {
00223       logger.msg(VERBOSE, "SendCommand: Failed: %s", result.str());
00224       return false;
00225     }
00226     while (!cb->ctrl) {
00227       timedin = cb->cond.wait(timeout * 1000);
00228       if (!timedin) {
00229         logger.msg(VERBOSE, "SendCommand: Timed out after %d ms", timeout * 1000);
00230         return false;
00231       }
00232     }
00233     if (!cb->responseok) {
00234       logger.msg(VERBOSE, "SendCommand: Failed: %s", cb->response);
00235       return false;
00236     }
00237 
00238     return true;
00239 
00240   } // end SendCommand
00241 
00242   bool FTPControl::SendCommand(const std::string& cmd, std::string& response,
00243                                int timeout) {
00244 
00245     bool timedin;
00246     GlobusResult result;
00247 
00248     cb->ctrl = false;
00249     result = globus_ftp_control_send_command(&control_handle, cmd.c_str(),
00250                                              &ControlCallback, cb);
00251     if (!result) {
00252       logger.msg(VERBOSE, "SendCommand: Failed: %s", result.str());
00253       return false;
00254     }
00255     while (!cb->ctrl) {
00256       timedin = cb->cond.wait(timeout * 1000);
00257       if (!timedin) {
00258         logger.msg(VERBOSE, "SendCommand: Timed out after %d ms", timeout * 1000);
00259         return false;
00260       }
00261     }
00262     if (!cb->responseok) {
00263       logger.msg(VERBOSE, "SendCommand: Failed: %s", cb->response);
00264       return false;
00265     }
00266 
00267     response = cb->response;
00268 
00269     return true;
00270 
00271   } // end SendCommand
00272 
00273   bool FTPControl::SendData(const std::string& data,
00274                             const std::string& filename, int timeout) {
00275 
00276     bool timedin;
00277     GlobusResult result;
00278 
00279     if (!SendCommand("DCAU N", timeout)) {
00280       logger.msg(VERBOSE, "SendData: Failed sending DCAU command");
00281       return false;
00282     }
00283 
00284     if (!SendCommand("TYPE I", timeout)) {
00285       logger.msg(VERBOSE, "SendData: Failed sending TYPE command");
00286       return false;
00287     }
00288 
00289     std::string response;
00290 
00291     if (!SendCommand("PASV", response, timeout)) {
00292       logger.msg(VERBOSE, "SendData: Failed sending PASV command");
00293       return false;
00294     }
00295 
00296     std::string::size_type pos1 = response.find('(');
00297     if (pos1 == std::string::npos) {
00298       logger.msg(VERBOSE, "SendData: Server PASV response parsing failed: %s",
00299                  response);
00300       return false;
00301     }
00302     std::string::size_type pos2 = response.find(')', pos1 + 1);
00303     if (pos2 == std::string::npos) {
00304       logger.msg(VERBOSE, "SendData: Server PASV response parsing failed: %s",
00305                  response);
00306       return false;
00307     }
00308 
00309     globus_ftp_control_host_port_t passive_addr;
00310     passive_addr.port = 0;
00311     unsigned short port_low, port_high;
00312     if (sscanf(response.substr(pos1 + 1, pos2 - pos1 - 1).c_str(),
00313                "%i,%i,%i,%i,%hu,%hu",
00314                &passive_addr.host[0],
00315                &passive_addr.host[1],
00316                &passive_addr.host[2],
00317                &passive_addr.host[3],
00318                &port_high,
00319                &port_low) == 6) {
00320       passive_addr.port = 256 * port_high + port_low;
00321     } else {
00322       logger.msg(VERBOSE, "SendData: Server PASV response parsing failed: %s",
00323                  response);
00324       return false;
00325     }
00326 
00327     result = globus_ftp_control_local_port(&control_handle, &passive_addr);
00328     if (!result) {
00329       logger.msg(VERBOSE, "SendData: Local port failed: %s", result.str());
00330       return false;
00331     }
00332 
00333     result = globus_ftp_control_local_type(&control_handle,
00334                                            GLOBUS_FTP_CONTROL_TYPE_IMAGE, 0);
00335     if (!result) {
00336       logger.msg(VERBOSE, "SendData: Local type failed: %s", result.str());
00337       return false;
00338     }
00339 
00340     cb->ctrl = false;
00341     cb->data = false;
00342     result = globus_ftp_control_send_command(&control_handle,
00343                                              ("STOR " + filename).c_str(),
00344                                              &ControlCallback, cb);
00345     if (!result) {
00346       logger.msg(VERBOSE, "SendData: Failed sending STOR command: %s",
00347                  result.str());
00348       return false;
00349     }
00350 
00351     result = globus_ftp_control_data_connect_write(&control_handle,
00352                                                    &DataConnectCallback, cb);
00353     if (!result) {
00354       logger.msg(VERBOSE, "SendData: Data connect write failed: %s",
00355                  result.str());
00356       return false;
00357     }
00358     while (!cb->data) {
00359       timedin = cb->cond.wait(timeout * 1000);
00360       if (!timedin) {
00361         logger.msg(VERBOSE, "SendData: Data connect write timed out after %d ms",
00362                    timeout * 1000);
00363         return false;
00364       }
00365     }
00366     while (!cb->ctrl) {
00367       timedin = cb->cond.wait(timeout * 1000);
00368       if (!timedin) {
00369         logger.msg(VERBOSE, "SendData: Data connect write timed out after %d ms",
00370                    timeout * 1000);
00371         return false;
00372       }
00373     }
00374     if (!cb->responseok) {
00375       logger.msg(VERBOSE, "SendData: Data connect write failed: %s",
00376                  cb->response);
00377       return false;
00378     }
00379 
00380     cb->data = false;
00381     cb->ctrl = false;
00382     result = globus_ftp_control_data_write(&control_handle,
00383                                            (globus_byte_t*)data.c_str(),
00384                                            data.size(), 0, GLOBUS_TRUE,
00385                                            &ReadWriteCallback, cb);
00386     if (!result) {
00387       logger.msg(VERBOSE, "SendData: Data write failed: %s", result.str());
00388       return false;
00389     }
00390     while (!cb->data) {
00391       timedin = cb->cond.wait(timeout * 1000);
00392       if (!timedin) {
00393         logger.msg(VERBOSE, "SendData: Data write timed out after %d ms",
00394                    timeout * 1000);
00395         return false;
00396       }
00397     }
00398     while (!cb->ctrl) {
00399       timedin = cb->cond.wait(timeout * 1000);
00400       if (!timedin) {
00401         logger.msg(VERBOSE, "SendData: Data write timed out after %d ms",
00402                    timeout * 1000);
00403         return false;
00404       }
00405     }
00406     if (!cb->responseok) {
00407       logger.msg(VERBOSE, "SendData: Data write failed: %s", cb->response);
00408       return false;
00409     }
00410 
00411     return true;
00412 
00413   } // end SendData
00414 
00415   bool FTPControl::Disconnect(int timeout) {
00416 
00417     bool timedin;
00418     GlobusResult result;
00419     bool res = true;
00420 
00421     if(!connected) return res;
00422 
00423     cb->ctrl = false;
00424     result = globus_ftp_control_quit(&control_handle, &ControlCallback, cb);
00425     if (!result) {
00426       logger.msg(VERBOSE, "Disconnect: Failed quitting: %s", result.str());
00427       return false;
00428     }
00429     while (!cb->ctrl) {
00430       timedin = cb->cond.wait(timeout * 1000);
00431       if (!timedin) {
00432         logger.msg(VERBOSE, "Disconnect: Quitting timed out after %d ms",
00433                    timeout * 1000);
00434         res = false;
00435       }
00436     }
00437 
00438     connected = false;
00439     cb->close = false;
00440     result = globus_ftp_control_force_close(&control_handle, &CloseCallback,
00441                                      cb->claim());
00442     if (!result) {
00443       cb->release();
00444       // Assuming only reason for failure here is that connection is 
00445       // already closed
00446       logger.msg(DEBUG, "Disconnect: Failed closing - ignoring: %s",
00447                  result.str());
00448     } else {
00449       // Need to wait for callback to make sure handle destruction will work
00450       // Hopefully forced close should never take long time
00451       while (!cb->close) {
00452         timedin = cb->cond.wait(timeout * 1000);
00453         if (!timedin) {
00454           logger.msg(ERROR, "Disconnect: Closing timed out after %d ms",
00455                      timeout * 1000);
00456           res = false;
00457         }
00458       }
00459     }
00460 
00461     result = globus_ftp_control_handle_destroy(&control_handle);
00462     if (!result) {
00463       // That means memory leak
00464       logger.msg(VERBOSE, "Disconnect: Failed to destroy handle: %s",
00465                  result.str());
00466       return false;
00467     }
00468 
00469     return true;
00470 
00471   } // end Disconnect
00472 
00473 } // namespace Arc