// fetchAndWritePAMPass attempts to authenticate with the iCAT server using PAM. // If PAM authentication is successful, it writes the returned PAM authentication token to a file for subsequent use in connections. func (con *Connection) fetchAndWritePAMPass(pamPassFile *os.File, ipassword *C.char) (*C.char, error) { var ( opassword *C.char errMsg *C.char ) if status := C.gorods_clientLoginPam(con.ccon, ipassword, C.int(con.Options.PAMPassExpire), &opassword, &errMsg); status != 0 { return nil, newError(Fatal, fmt.Sprintf("iRODS Connect Failed: clientLoginPam error, invalid password?")) } if er := pamPassFile.Truncate(0); er != nil { return nil, newError(Fatal, fmt.Sprintf("iRODS Connect Failed: Unable to write new password to PAMPassFile")) } pamPassFormat := strconv.Itoa(int(time.Now().Unix())) + ":" + C.GoString(opassword) if _, er := pamPassFile.WriteString(pamPassFormat); er != nil { return nil, newError(Fatal, fmt.Sprintf("iRODS Connect Failed: Unable to write new password to PAMPassFile")) } return opassword, nil }
// NewConnection creates a connection to an iRODS iCAT server. EnvironmentDefined and UserDefined // constants are used in ConnectionOptions{ Type: ... }). // When EnvironmentDefined is specified, the options stored in ~/.irods/irods_environment.json will be used. // When UserDefined is specified you must also pass Host, Port, Username, and Zone. Password // should be set unless using an anonymous user account with tickets. func NewConnection(opts *ConnectionOptions) (*Connection, error) { con := new(Connection) con.Options = opts var ( status C.int errMsg *C.char ipassword *C.char opassword *C.char ) // Are we passing env values? if con.Options.Type == UserDefined { host := C.CString(con.Options.Host) port := C.int(con.Options.Port) username := C.CString(con.Options.Username) zone := C.CString(con.Options.Zone) defer C.free(unsafe.Pointer(host)) defer C.free(unsafe.Pointer(username)) defer C.free(unsafe.Pointer(zone)) // BUG(jjacquay712): iRODS C API code outputs errors messages, need to implement connect wrapper (gorods_connect_env) from a lower level to suppress this output // https://github.com/irods/irods/blob/master/iRODS/lib/core/src/rcConnect.cpp#L109 if status = C.gorods_connect_env(&con.ccon, host, port, username, zone, &errMsg); status != 0 { return nil, newError(Fatal, fmt.Sprintf("iRODS Connect Failed: %v", C.GoString(errMsg))) } } else { if status = C.gorods_connect(&con.ccon, &errMsg); status != 0 { return nil, newError(Fatal, fmt.Sprintf("iRODS Connect Failed: %v", C.GoString(errMsg))) } } ipassword = C.CString(con.Options.Password) defer C.free(unsafe.Pointer(ipassword)) if con.Options.AuthType == 0 { con.Options.AuthType = PasswordAuth // Options: PasswordAuth PAMAuth } if con.Options.PAMPassExpire == 0 { con.Options.PAMPassExpire = 1 // Default expiration: 1 hour } var ( pamPassFile *os.File pamFileErr error size int64 ) if con.Options.AuthType == PAMAuth { // Was a PAM token passed? if con.Options.PAMToken != "" { // Use it, pass directly to clientLoginWithPassword opassword = C.CString(con.Options.PAMToken) defer C.free(unsafe.Pointer(opassword)) } else if con.Options.PAMPassFile == "" { // Continue with auth using .Password (ipassword) option, Check to see if PAMPassFile option is not set // It's not, fetch password and just keep in memory if status = C.gorods_clientLoginPam(con.ccon, ipassword, C.int(con.Options.PAMPassExpire), &opassword, &errMsg); status != 0 { return nil, newError(Fatal, fmt.Sprintf("iRODS Connect Failed: clientLoginPam error, invalid password?")) } defer C.free(unsafe.Pointer(opassword)) } else { // There is a PAM file path set, save password to FS for subsequent use // Does the file/dir exist? if finfo, err := os.Stat(con.Options.PAMPassFile); err == nil { if !finfo.IsDir() { // Open file here pamPassFile, pamFileErr = os.OpenFile(con.Options.PAMPassFile, os.O_RDWR, 0666) if pamFileErr != nil { return nil, newError(Fatal, fmt.Sprintf("iRODS Connect Failed: Problem opening PAMPassFile at %v", con.Options.PAMPassFile)) } size = finfo.Size() } else { return nil, newError(Fatal, fmt.Sprintf("iRODS Connect Failed: PAMPassFile is a directory durp")) } } else { // Create file here pamPassFile, pamFileErr = os.Create(con.Options.PAMPassFile) if pamFileErr != nil { return nil, newError(Fatal, fmt.Sprintf("iRODS Connect Failed: Problem creating PAMPassFile at %v", con.Options.PAMPassFile)) } } // Is this an old password file? if size > 0 { fileBtz := make([]byte, size) if _, er := pamPassFile.Read(fileBtz); er != nil { return nil, newError(Fatal, fmt.Sprintf("iRODS Connect Failed: Problem reading PAMPassFile at %v", con.Options.PAMPassFile)) } fileStr := string(fileBtz) fileSplit := strings.Split(fileStr, ":") unixTimeStamp, _ := strconv.Atoi(fileSplit[0]) pamPassword := fileSplit[1] now := int(time.Now().Unix()) // Check to see if the password has expired if (unixTimeStamp + (con.Options.PAMPassExpire * 60)) <= now { // we're expired, refresh opassword, pamFileErr = con.fetchAndWritePAMPass(pamPassFile, ipassword) if pamFileErr != nil { return nil, pamFileErr } } else { // It's still good, use it opassword = C.CString(pamPassword) } defer C.free(unsafe.Pointer(opassword)) } else { // Nope, it's new. Write to the file opassword, pamFileErr = con.fetchAndWritePAMPass(pamPassFile, ipassword) if pamFileErr != nil { return nil, pamFileErr } defer C.free(unsafe.Pointer(opassword)) } } } else if con.Options.AuthType == PasswordAuth { opassword = ipassword } if status = C.clientLoginWithPassword(con.ccon, opassword); status != 0 { // if status == C.CAT_PASSWORD_EXPIRED { // fmt.Printf("expired:%v\n", pamPassFile.Name()) // } if con.Options.AuthType == PAMAuth { if pamPassFile != nil { // Failure, clear out file for another try. if er := pamPassFile.Truncate(int64(0)); er != nil { return nil, newError(Fatal, fmt.Sprintf("iRODS Connect Failed: Unable to truncate PAMPassFile: %v", er)) } return nil, newError(Fatal, fmt.Sprintf("iRODS Connect Failed: clientLoginWithPassword error, expired password?")) } } return nil, newError(Fatal, fmt.Sprintf("iRODS Connect Failed: clientLoginWithPassword error, invalid password?")) } if con.Options.AuthType == PAMAuth { con.PAMToken = C.GoString(opassword) } con.cconBuffer = make(chan *C.rcComm_t, 1) con.cconBuffer <- con.ccon if status == 0 { con.Connected = true } else { return nil, newError(Fatal, fmt.Sprintf("iRODS Connect Failed: %v", C.GoString(errMsg))) } con.SetThreads(opts.Threads) if con.Options.Ticket != "" { if err := con.SetTicket(con.Options.Ticket); err != nil { return nil, err } } if !con.Options.FastInit { if err := con.init(); err != nil { return nil, err } } return con, nil }