/** \file apps/obexftpd.c OBEX file server. ObexFTP library - language bindings for OBEX file transfer. Copyright (c) 2003-2006 Christian W. Zuckschwerdt Alan Zhang Hendrik Sattler Frode Isaksen ObexFTP is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with ObexFTP. If not, see . */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #ifdef HAVE_BLUETOOTH #include #define bdaddr_t BTH_ADDR #endif #define S_IRGRP 0 #define S_IROTH 0 #define S_IWGRP 0 #define S_IWOTH 0 #define S_IXGRP 0 #define S_IXOTH 0 #define sleep(n) Sleep((n) * 1000) #define mkdir(dir,mode) _mkdir(dir) #define lstat stat #else #include #include #include #include #endif /* just until there is a server layer in obexftp */ #include #include #include #include /* define this to "", "\r\n" or "\n" */ #define EOLCHARS "\n" /* Application defined headers */ #define HDR_CREATOR 0xcf /* so we don't require OpenOBEX 1.3 */ #define WORK_PATH_MAX 128 #define CUR_DIR "./" #ifdef HAVE_BLUETOOTH static bdaddr_t *bt_src = NULL; #endif static char *device = NULL; static int channel = 10; /* OBEX_PUSH_HANDLE */ static char init_work_path[WORK_PATH_MAX]; volatile int finished = 0; volatile int success = 0; volatile int obexftpd_reset = 0; uint32_t connection_id = 0; int verbose = 0; // this whole thing needs a review: static int parsehostport(const char *name, char **host, int *port) { struct hostent *e; char *p, *s; uint8_t n[4]; p = strdup(name); *port = 650; if ((s=strchr(p, ':'))) { *s = '\0'; *port = atoi(++s); } *host = "0.0.0.0"; if (sscanf(p, "%hhu.%hhu.%hhu.%hhu", &n[0], &n[1], &n[2], &n[3]) == 4) { *host = strdup(p); } else { e = gethostbyname(p); if (e) { *host = e->h_addr_list[0]; // inet_ntoa needed! } } free(p); return 0; } //BEGIN of compositor the folder listing XML document struct rawdata_stream { char *data; unsigned int block_size; unsigned int size; unsigned int max_size; }; static struct rawdata_stream* INIT_RAWDATA_STREAM(unsigned int size) { struct rawdata_stream *stream = malloc(sizeof(*stream)); if (NULL == stream) { return NULL; } stream->data = malloc(size); if (NULL == stream->data) { free(stream); return NULL; } memset(stream->data,0,size); stream->size = 0; stream->block_size = size; stream->max_size = size; return stream; } static int ADD_RAWDATA_STREAM_DATA(struct rawdata_stream *stream, const char *data) { char *databuf; unsigned int size; if (data == NULL) return 0; size = strlen(data); if (size == 0) return 0; if ((size + stream->size) >= stream->max_size) { while((size + stream->size) >= stream->max_size) { stream->max_size += stream->block_size; } databuf = realloc(stream->data, stream->max_size); if (NULL == databuf) { fprintf(stderr, "realloc() failed\n"); return -1; } stream->data = databuf; } strcat(stream->data, data); stream->size += size; return size; } static void FREE_RAWDATA_STREAM(struct rawdata_stream *stream) { free(stream->data); free(stream); } #define FL_XML_VERSION(stream) \ ADD_RAWDATA_STREAM_DATA(stream, "" EOLCHARS); #define FL_XML_TYPE(stream) \ ADD_RAWDATA_STREAM_DATA(stream, "" EOLCHARS); #define FL_XML_BODY_BEGIN(stream) \ ADD_RAWDATA_STREAM_DATA(stream, "" EOLCHARS); #define FL_XML_BODY_END(stream) \ ADD_RAWDATA_STREAM_DATA(stream, "" EOLCHARS); #define FL_XML_BODY_ITEM_BEGIN(stream) \ ADD_RAWDATA_STREAM_DATA(stream, "<"); #define FL_XML_BODY_ITEM_END(stream) \ ADD_RAWDATA_STREAM_DATA(stream, "/>" EOLCHARS); inline static void FL_XML_BODY_FOLDERNAME(struct rawdata_stream *stream, char *name) { ADD_RAWDATA_STREAM_DATA(stream, "folder name=\""); ADD_RAWDATA_STREAM_DATA(stream, name); ADD_RAWDATA_STREAM_DATA(stream, "\" "); } inline static void FL_XML_BODY_FILENAME(struct rawdata_stream *stream, char *name) { ADD_RAWDATA_STREAM_DATA(stream, "file name=\""); ADD_RAWDATA_STREAM_DATA(stream, name); ADD_RAWDATA_STREAM_DATA(stream, "\" "); } inline static void FL_XML_BODY_SIZE(struct rawdata_stream *stream, unsigned int size) { const char* format = "size=\"%d\" "; char str_size[sizeof(format)+14]; snprintf(str_size,sizeof(str_size), format, size); ADD_RAWDATA_STREAM_DATA(stream, str_size); } inline static void FL_XML_BODY_PERM(struct rawdata_stream *stream, mode_t file, mode_t dir) { const char* format = "user-perm=\"%s%s%s\" "; char str_perm[sizeof(format)]; snprintf(str_perm,sizeof(str_perm),format, (file & (S_IRUSR|S_IRGRP|S_IROTH)? "R": ""), (file & (S_IWUSR|S_IWGRP|S_IWOTH)? "W": ""), (dir & (S_IWUSR|S_IWGRP|S_IWOTH)? "D": "")); ADD_RAWDATA_STREAM_DATA(stream,str_perm); } inline static void FL_XML_BODY_TIME(struct rawdata_stream *stream, const char* type, time_t time) { struct tm* tm = gmtime(&time); char str_tm[sizeof("=\"yyyymmddThhmmssZ\" ")]; if (tm == NULL || stream == NULL || strftime(str_tm,sizeof(str_tm),"=\"%Y%m%dT%H%M%SZ\" ",tm) == 0) return; ADD_RAWDATA_STREAM_DATA(stream, type); ADD_RAWDATA_STREAM_DATA(stream, str_tm); } #define FL_XML_BODY_MTIME(stream,time) \ FL_XML_BODY_TIME(stream,"modified",time) #define FL_XML_BODY_CTIME(stream,time) \ FL_XML_BODY_TIME(stream,"created",time) #define FL_XML_BODY_ATIME(stream,time) \ FL_XML_BODY_TIME(stream,"accessed",time) //END of compositor the folder listing XML document inline static int is_type_fl(const char *type) { return (type && strcmp(type, XOBEX_LISTING) == 0); } static void connect_server(obex_t *handle, obex_object_t *object) { obex_headerdata_t hv; uint8_t hi; uint32_t hlen; uint8_t *target = NULL; int target_len = 0; //printf("%s()\n", __FUNCTION__); while (OBEX_ObjectGetNextHeader(handle, object, &hi, &hv, &hlen)) { switch(hi) { case OBEX_HDR_TARGET: if (0 < hlen) { if( (target = malloc(hlen))) { target_len = hlen; memcpy(target,hv.bs,target_len); } } break; default: printf("%s() Skipped header %02x\n", __FUNCTION__, hi); break; } } OBEX_ObjectSetRsp(object, OBEX_RSP_SUCCESS, OBEX_RSP_SUCCESS); hv.bq4 = connection_id; if(OBEX_ObjectAddHeader(handle, object, OBEX_HDR_CONNECTION, hv, sizeof(hv.bq4), OBEX_FL_FIT_ONE_PACKET) < 0 ) { fprintf(stderr, "Error adding header CONNECTION\n"); OBEX_ObjectDelete(handle, object); return; } if (target && target_len) { hv.bs = target; if(OBEX_ObjectAddHeader(handle,object,OBEX_HDR_WHO, hv,target_len,OBEX_FL_FIT_ONE_PACKET) < 0 ) { fprintf(stderr, "Error adding header WHO\n"); OBEX_ObjectDelete(handle, object); return; } free(target); } } static void set_server_path(obex_t *handle, obex_object_t *object) { char *name = NULL; char fullname[WORK_PATH_MAX]; // "Backup Level" and "Don't Create" flag in first byte uint8_t setpath_nohdr_dummy = 0; uint8_t *setpath_nohdr_data; obex_headerdata_t hv; uint8_t hi; uint32_t hlen; OBEX_ObjectGetNonHdrData(object, &setpath_nohdr_data); if (NULL == setpath_nohdr_data) { setpath_nohdr_data = &setpath_nohdr_dummy; printf("nohdr data not found\n"); } printf("nohdr data: %x\n", *setpath_nohdr_data); while(OBEX_ObjectGetNextHeader(handle, object, &hi, &hv, &hlen)) { switch(hi) { case OBEX_HDR_NAME: printf("%s() Found name\n", __FUNCTION__); if (0 < hlen) { if( (name = malloc(hlen / 2))) { OBEX_UnicodeToChar((uint8_t*)name, hv.bs, hlen); printf("name:%s\n", name); } } else { if (verbose) printf("set path to root\n"); chdir(init_work_path); } break; default: printf("%s() Skipped header %02x\n", __FUNCTION__, hi); } } if (name) { if (strstr(name, "/../")) { OBEX_ObjectSetRsp(object, OBEX_RSP_CONTINUE, OBEX_RSP_FORBIDDEN); } else { strcpy(fullname, CUR_DIR); strncat(fullname, name, sizeof(fullname)-1); if ((*setpath_nohdr_data & 2) == 0) { if (verbose) printf("mkdir %s\n", name); if (mkdir(fullname, 0755) < 0) { perror("requested mkdir failed"); } } if (verbose) printf("Set path to %s\n",fullname); if (chdir(fullname) < 0) { perror("requested chdir failed\n"); OBEX_ObjectSetRsp(object, OBEX_RSP_CONTINUE, OBEX_RSP_FORBIDDEN); } } free(name); name = NULL; } } // // Get the filesize in a "portable" way // static int get_filesize(const char *filename) { struct stat stats; stat(filename, &stats); if (S_ISDIR(stats.st_mode)) { fprintf(stderr,"GET of directories not implemented !!!!\n"); return -1; } return (int) stats.st_size; } // // Read a file and alloc a buffer for it // static uint8_t* easy_readfile(const char *filename, int *file_size) { int actual; int fd; uint8_t *buf; *file_size = get_filesize(filename); printf("name=%s, size=%d\n", filename, *file_size); fd = open(filename, O_RDONLY, 0); if (fd == -1) { return NULL; } buf = malloc(*file_size); if(buf == NULL) { return NULL; } actual = read(fd, buf, *file_size); close(fd); *file_size = actual; return buf; } static void get_server(obex_t *handle, obex_object_t *object) { uint8_t *buf = NULL; obex_headerdata_t hv; uint8_t hi; uint32_t hlen; int file_size; char *name = NULL; char *type = NULL; printf("%s()\n", __FUNCTION__); while(OBEX_ObjectGetNextHeader(handle, object, &hi, &hv, &hlen)) { switch(hi) { case OBEX_HDR_NAME: printf("%s() Found name\n", __FUNCTION__); if( (name = malloc(hlen / 2))) { OBEX_UnicodeToChar((uint8_t*)name, hv.bs, hlen); printf("name:%s\n", name); } break; case OBEX_HDR_TYPE: if( (type = malloc(hlen + 1))) { strcpy(type, (char *)hv.bs); } printf("%s() type:%s\n", __FUNCTION__, type); case 0xbe: // user-defined inverse push printf("%s() Found inverse push req\n", __FUNCTION__); printf("data:%02x\n", hv.bq1); break; case OBEX_HDR_APPARAM: printf("%s() Found apparam\n", __FUNCTION__); printf("name:%d (%02x %02x ...)\n", hlen, *hv.bs, *(hv.bs+1)); break; default: printf("%s() Skipped header %02x\n", __FUNCTION__, hi); } } //fprintf(stderr, "%s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__); if (is_type_fl(type)) { struct dirent *dirp; DIR *dp; struct stat statdir; struct stat statbuf; char *filename; struct rawdata_stream *xmldata; xmldata = INIT_RAWDATA_STREAM(512); if (NULL == xmldata) goto out; FL_XML_VERSION(xmldata); FL_XML_TYPE(xmldata); FL_XML_BODY_BEGIN(xmldata); stat(CUR_DIR, &statdir); dp = opendir(CUR_DIR); while(NULL != dp && NULL != (dirp = readdir(dp))) { if (0 == strcmp(dirp->d_name, ".") || 0 == strcmp(dirp->d_name, "..")) continue; FL_XML_BODY_ITEM_BEGIN(xmldata); //Adding 1 bytes due to containing '\0' filename = malloc(strlen(CUR_DIR) + strlen(dirp->d_name) + 1); strcpy(filename, CUR_DIR); strcat(filename, dirp->d_name); lstat(filename, &statbuf); if (0 == S_ISDIR(statbuf.st_mode)) //it is a file FL_XML_BODY_FILENAME(xmldata, dirp->d_name); else //it is a directory FL_XML_BODY_FOLDERNAME(xmldata, dirp->d_name); FL_XML_BODY_SIZE(xmldata, statbuf.st_size); FL_XML_BODY_PERM(xmldata, statbuf.st_mode, statdir.st_mode); FL_XML_BODY_MTIME(xmldata, statbuf.st_mtime); FL_XML_BODY_CTIME(xmldata, statbuf.st_ctime); FL_XML_BODY_ATIME(xmldata, statbuf.st_atime); FL_XML_BODY_ITEM_END(xmldata); //printf("---filename:%s\n", filename); //fprintf(stderr, "%s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__); free(filename); filename = NULL; //fprintf(stderr, "%s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__); } //fprintf(stderr, "%s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__); FL_XML_BODY_END(xmldata); closedir(dp); printf("xml doc:%s\n", xmldata->data); //composite the obex obejct OBEX_ObjectSetRsp(object, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS); hv.bs = (uint8_t *)xmldata->data; OBEX_ObjectAddHeader(handle, object, OBEX_HDR_BODY, hv, xmldata->size, 0); hv.bq4 = xmldata->size; OBEX_ObjectAddHeader(handle, object, OBEX_HDR_LENGTH, hv, sizeof(uint32_t), 0); //fprintf(stderr, "%s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__); FREE_RAWDATA_STREAM(xmldata); //fprintf(stderr, "%s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__); } else if (name) { printf("%s() Got a request for %s\n", __FUNCTION__, name); buf = easy_readfile(name, &file_size); if(buf == NULL) { printf("Can't find file %s\n", name); OBEX_ObjectSetRsp(object, OBEX_RSP_NOT_FOUND, OBEX_RSP_NOT_FOUND); return; } OBEX_ObjectSetRsp(object, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS); hv.bs = buf; OBEX_ObjectAddHeader(handle, object, OBEX_HDR_BODY, hv, file_size, 0); hv.bq4 = file_size; OBEX_ObjectAddHeader(handle, object, OBEX_HDR_LENGTH, hv, sizeof(uint32_t), 0); } else { printf("%s() Got a GET without a name-header!\n", __FUNCTION__); OBEX_ObjectSetRsp(object, OBEX_RSP_NOT_FOUND, OBEX_RSP_NOT_FOUND); return; } //fprintf(stderr, "%s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__); if (NULL != buf) { free(buf); } //fprintf(stderr, "%s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__); out: if (NULL != name) { free(name); } //fprintf(stderr, "%s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__); if (NULL != type) { free(type); } //fprintf(stderr, "%s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__); return; } /* * Function safe_save_file () * * First remove path and add "/tmp/". Then save. * */ static int safe_save_file(char *name, const uint8_t *buf, int len) { char *s = NULL; char filename[255] = {0,}; int fd; int actual; printf("Filename = %s\n", name); s = strrchr(name, '/'); if (s == NULL) s = name; else s++; strncat(filename, s, 250); fd = open(filename, O_RDWR | O_CREAT | O_EXCL, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); if ( fd < 0) { perror( filename); return -1; } actual = write(fd, buf, len); close(fd); printf( "Wrote %s (%d bytes)\n", filename, actual); return actual; } /* * Function put_done() * * Parse what we got from a PUT * */ static void put_done(obex_t *handle, obex_object_t *object, int final) { obex_headerdata_t hv; uint8_t hi; uint32_t hlen; const uint8_t *body = NULL; int body_len = 0; static char *name = NULL; char fullname[WORK_PATH_MAX]; struct stat statbuf; //char *namebuf = NULL; fprintf(stderr, "put_done>>>\n"); while(OBEX_ObjectGetNextHeader(handle, object, &hi, &hv, &hlen)) { switch(hi) { case OBEX_HDR_BODY: body = hv.bs; body_len = hlen; fprintf(stderr, "hv.bs=%p, hlen=%d\n", hv.bs, hlen); break; case OBEX_HDR_NAME: if (NULL != name) { free(name); } if( (name = malloc(hlen / 2))) { OBEX_UnicodeToChar((uint8_t *)name, hv.bs, hlen); fprintf(stderr, "put file name: %s\n", name); } break; case OBEX_HDR_LENGTH: printf("HEADER_LENGTH = %d\n", hv.bq4); break; case HDR_CREATOR: printf("CREATORID = %#x\n", hv.bq4); break; default: printf("%s () Skipped header %02x\n", __FUNCTION__ , hi); } } if(!body) { printf("Got a PUT without a body\n"); OBEX_ObjectSetRsp(object, OBEX_RSP_CONTINUE, OBEX_RSP_SUCCESS); } if(!name) { name = strdup("OBEX_PUT_Unknown_object"); printf("Got a PUT without a name. Setting name to %s\n", name); } if (body) { safe_save_file(name, body, body_len); } if(final && !body) { strcpy(fullname, CUR_DIR); strcat(fullname, name); if (!stat(fullname, &statbuf)) { perror("stat failed"); } if (S_ISDIR(statbuf.st_mode)) { printf("Removing dir %s\n", name); rmdir(fullname); } else { printf("Deleting file %s\n", name); unlink(fullname); } } if (final) { free(name); name = NULL; fprintf(stderr, "<<= strlen(progress)) i = 0; switch(obex_cmd) { case OBEX_CMD_PUT: fprintf(stderr, "obex_ev_progress: obex_cmd_put\n"); put_done(handle, obj, 0); break; default: break; } break; case OBEX_EV_ABORT: /* Request was aborted */ printf("%s() OBEX_EV_ABORT: mode=%02x, obex_cmd=%02x, obex_rsp=%02x\n", __func__, mode, obex_cmd, obex_rsp); break; case OBEX_EV_STREAMEMPTY: //(void) cli_fillstream(cli, object); break; case OBEX_EV_UNEXPECTED: /* Unexpected data, not fatal */ break; default: printf("%s() Unhandled event %d\n", __func__, event); break; } } static void start_server(int transport) { int use_sdp = 0; obex_t *handle = NULL; struct sockaddr_in saddr; if (NULL == getcwd(init_work_path, WORK_PATH_MAX) && ERANGE == errno) { fprintf(stderr, "Wah, work path is too long, exceed %d.\n", WORK_PATH_MAX); exit(0); } if (transport==OBEX_TRANS_BLUETOOTH && 0 > obexftp_sdp_register_ftp(channel)) { //OBEX_Cleanup(handle); fprintf(stderr, "register to SDP Server failed.\n"); } else { use_sdp = 1; } reset: handle = OBEX_Init(transport, obex_event, 0); if (NULL == handle) { perror("failed to init obex."); exit(-1); } switch (transport) { case OBEX_TRANS_INET: saddr.sin_family = AF_INET; saddr.sin_port = htons(channel); #ifdef _WIN32 saddr.sin_addr.s_addr = inet_addr(device); #else (void) inet_aton(device, &saddr.sin_addr); #endif //InOBEX_ServerRegister(handle); /* always port 650 */ if (0 > OBEX_ServerRegister(handle, (struct sockaddr *)&saddr, sizeof(saddr))) { perror("failed to register inet server"); exit(-1); } break; #ifdef HAVE_BLUETOOTH case OBEX_TRANS_BLUETOOTH: if (0 > BtOBEX_ServerRegister(handle, bt_src, channel)) { perror("failed to register bluetooth server"); exit(-1); } break; #endif case OBEX_TRANS_IRDA: if (0 > IrOBEX_ServerRegister(handle, "")) { perror("failed to register IrDA server"); exit(-1); } break; case OBEX_TRANS_CUSTOM: /* A simple Ericsson protocol session perhaps? */ default: fprintf(stderr, "Transport type unknown\n"); exit(-1); } printf("Waiting for connection...\n"); (void) OBEX_ServerAccept(handle, obex_event, NULL); while (!finished) { //printf("Handling connection...\n"); OBEX_HandleInput(handle, 1); } OBEX_Cleanup(handle); sleep(1); /* throttle */ if (obexftpd_reset) { fprintf(stderr, "obexftpd reset\n"); obexftpd_reset = 0; finished = 0; success = 0; goto reset; } if (use_sdp) { fprintf(stderr, "sdp unregister\n"); obexftp_sdp_unregister_ftp(); } } int main(int argc, char *argv[]) { int c; while (1) { int option_index = 0; static struct option long_options[] = { {"irda", no_argument, NULL, 'i'}, {"bluetooth", optional_argument, NULL, 'b'}, {"tty", required_argument, NULL, 't'}, {"network", required_argument, NULL, 'n'}, {"chdir", required_argument, NULL, 'c'}, {"verbose", no_argument, NULL, 'v'}, {"version", no_argument, NULL, 'V'}, {"help", no_argument, NULL, 'h'}, {"usage", no_argument, NULL, 'h'}, {0, 0, 0, 0} }; c = getopt_long (argc, argv, "-ib::t:n:c:vVh", long_options, &option_index); if (c == -1) break; switch (c) { case 'i': start_server(OBEX_TRANS_IRDA); break; case 'b': if (optarg) { channel = atoi(optarg); } start_server(OBEX_TRANS_BLUETOOTH); fprintf(stderr, "server end\n"); break; case 'n': parsehostport(optarg, &device, &channel); //channel = atoi(optarg); start_server(OBEX_TRANS_INET); fprintf(stderr, "server end\n"); break; case 't': printf("accepting on tty not implemented yet."); /* start_server(OBEX_TRANS_CUSTOM); optarg */ break; case 'c': chdir(optarg); break; case 'v': verbose++; break; case 'V': printf("ObexFTPd %s\n", VERSION); break; case 'h': printf("ObexFTPd %s\n", VERSION); printf("Usage: %s [-c ] [-v] [-i | -b | -t | -n ]\n" "Recieve files from/to Mobile Equipment.\n" "Copyright (c) 2003-2006 Christian W. Zuckschwerdt, Alan Zhang,\n" " Hendrik Sattler, Frode Isaksen\n" "\n" " -i, --irda accept IrDA connections\n" " -b, --bluetooth [] accept bluetooth connections\n" " -t, --tty accept connections from this tty\n" " -n, --network accept network connections\n" "\n" " -c, --chdir set a default basedir\n" " -v, --verbose verbose messages\n" "\n" " -V, --version print version info\n" " -h, --help, --usage this help text\n", argv[0]); exit(0); default: printf("Try `%s --help' for more information.\n", argv[0]); } } if (optind < argc) { fprintf(stderr, "non-option ARGV-elements: "); while (optind < argc) fprintf(stderr, "%s ", argv[optind++]); fprintf(stderr, "\n"); exit (-1); } exit (0); }