void janus_videocall_create_session(janus_plugin_session *handle, int *error) { if(g_atomic_int_get(&stopping) || !g_atomic_int_get(&initialized)) { *error = -1; return; } janus_videocall_session *session = (janus_videocall_session *)g_malloc0(sizeof(janus_videocall_session)); if(session == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); *error = -2; return; } session->handle = handle; session->has_audio = FALSE; session->has_video = FALSE; session->audio_active = TRUE; session->video_active = TRUE; session->bitrate = 0; /* No limit */ session->peer = NULL; session->username = NULL; janus_mutex_init(&session->rec_mutex); session->destroyed = 0; g_atomic_int_set(&session->hangingup, 0); handle->plugin_handle = session; return; }
janus_text2pcap *janus_text2pcap_create(const char *dir, const char *filename, int truncate, gboolean text) { janus_text2pcap *tp; char newname[1024]; char *fname; FILE *f; if(truncate < 0) return NULL; /* Copy given filename or generate a random one */ if (filename == NULL) { g_snprintf(newname, sizeof(newname), "janus-text2pcap-%"SCNu32".%s", janus_random_uint32(), text ? "txt" : "pcap"); } else { g_strlcpy(newname, filename, sizeof(newname)); } if(dir != NULL) { /* Create the directory, if needed */ if(janus_mkdir(dir, 0755) < 0) { JANUS_LOG(LOG_ERR, "mkdir error: %d\n", errno); return NULL; } fname = g_strdup_printf("%s/%s", dir, newname); } else { fname = g_strdup(newname); } /* Try opening the file now */ f = fopen(fname, "ab"); if (f == NULL) { JANUS_LOG(LOG_ERR, "fopen(%s) error: %d\n", fname, errno); g_free(fname); return NULL; } /* Create the text2pcap instance */ tp = g_malloc(sizeof(janus_text2pcap)); tp->filename = fname; tp->file = f; tp->truncate = truncate; tp->text = text; g_atomic_int_set(&tp->writable, 1); janus_mutex_init(&tp->mutex); /* If we're saving to .pcap directly, generate a global header */ if(!text) { janus_text2pcap_global_header header = { 0xa1b2c3d4, 2, 4, 0, 0, 65535, 1 }; fwrite(&header, sizeof(char), sizeof(header), f); } return tp; }
janus_transport_session *janus_transport_session_create(void *transport_p, void (*p_free)(void *)) { janus_transport_session *tp = g_malloc0(sizeof(janus_transport_session)); if(tp == NULL) return NULL; tp->transport_p = transport_p; tp->p_free = p_free; g_atomic_int_set(&tp->destroyed, 0); janus_refcount_init(&tp->ref, janus_transport_session_free); janus_mutex_init(&tp->mutex); return tp; }
/* Plugin implementation */ int janus_videocall_init(janus_callbacks *callback, const char *config_path) { if(g_atomic_int_get(&stopping)) { /* Still stopping from before */ return -1; } if(callback == NULL || config_path == NULL) { /* Invalid arguments */ return -1; } /* Read configuration */ char filename[255]; g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_VIDEOCALL_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if(config != NULL) { janus_config_print(config); janus_config_item *events = janus_config_get_item_drilldown(config, "general", "events"); if(events != NULL && events->value != NULL) notify_events = janus_is_true(events->value); if(!notify_events && callback->events_is_enabled()) { JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_VIDEOCALL_NAME); } } janus_config_destroy(config); config = NULL; sessions = g_hash_table_new(g_str_hash, g_str_equal); janus_mutex_init(&sessions_mutex); messages = g_async_queue_new_full((GDestroyNotify) janus_videocall_message_free); /* This is the callback we'll need to invoke to contact the gateway */ gateway = callback; g_atomic_int_set(&initialized, 1); GError *error = NULL; /* Start the sessions watchdog */ watchdog = g_thread_try_new("videocall watchdog", &janus_videocall_watchdog, NULL, &error); if(error != NULL) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoCall watchdog thread...\n", error->code, error->message ? error->message : "??"); return -1; } /* Launch the thread that will handle incoming messages */ handler_thread = g_thread_try_new("videocall handler", janus_videocall_handler, NULL, &error); if(error != NULL) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the VideoCall handler thread...\n", error->code, error->message ? error->message : "??"); return -1; } JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_VIDEOCALL_NAME); return 0; }
/* Plugin implementation */ int janus_echotest_init(janus_callbacks *callback, const char *config_path) { if(g_atomic_int_get(&stopping)) { /* Still stopping from before */ return -1; } if(callback == NULL || config_path == NULL) { /* Invalid arguments */ return -1; } /* Read configuration */ char filename[255]; g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_ECHOTEST_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if(config != NULL) janus_config_print(config); /* This plugin actually has nothing to configure... */ janus_config_destroy(config); config = NULL; sessions = g_hash_table_new(NULL, NULL); janus_mutex_init(&sessions_mutex); messages = g_async_queue_new_full((GDestroyNotify) janus_echotest_message_free); /* This is the callback we'll need to invoke to contact the gateway */ gateway = callback; g_atomic_int_set(&initialized, 1); GError *error = NULL; /* Start the sessions watchdog */ watchdog = g_thread_try_new("etest watchdog", &janus_echotest_watchdog, NULL, &error); if(error != NULL) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the EchoTest watchdog thread...\n", error->code, error->message ? error->message : "??"); return -1; } /* Launch the thread that will handle incoming messages */ handler_thread = g_thread_try_new("janus echotest handler", janus_echotest_handler, NULL, &error); if(error != NULL) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the EchoTest handler thread...\n", error->code, error->message ? error->message : "??"); return -1; } JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_ECHOTEST_NAME); return 0; }
/* Plugin implementation */ int janus_videocall_init(janus_callbacks *callback, const char *config_path) { if(stopping) { /* Still stopping from before */ return -1; } if(callback == NULL || config_path == NULL) { /* Invalid arguments */ return -1; } /* Read configuration */ char filename[255]; sprintf(filename, "%s/%s.cfg", config_path, JANUS_VIDEOCALL_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if(config != NULL) janus_config_print(config); /* This plugin actually has nothing to configure... */ janus_config_destroy(config); config = NULL; sessions = g_hash_table_new(g_str_hash, g_str_equal); janus_mutex_init(&sessions_mutex); messages = g_async_queue_new_full((GDestroyNotify) janus_videocall_message_free); /* This is the callback we'll need to invoke to contact the gateway */ gateway = callback; initialized = 1; /* Launch the thread that will handle incoming messages */ GError *error = NULL; handler_thread = g_thread_try_new("janus videocall handler", janus_videocall_handler, NULL, &error); if(error != NULL) { initialized = 0; /* Something went wrong... */ JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch thread...\n", error->code, error->message ? error->message : "??"); return -1; } JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_VIDEOCALL_NAME); return 0; }
/* Plugin implementation */ int janus_serial_init(janus_callbacks *callback, const char *config_path) { if(g_atomic_int_get(&stopping)) { /* Still stopping from before */ return -1; } if(callback == NULL || config_path == NULL) { /* Invalid arguments */ return -1; } /* Read configuration */ char filename[255]; g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_SERIAL_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if(config != NULL) janus_config_print(config); /* This plugin actually has nothing to configure... */ janus_config_destroy(config); config = NULL; sessions = g_hash_table_new(NULL, NULL); janus_mutex_init(&sessions_mutex); messages = g_async_queue_new_full((GDestroyNotify) janus_serial_message_free); /* This is the callback we'll need to invoke to contact the gateway */ gateway = callback; g_atomic_int_set(&initialized, 1); GError *error = NULL; /* Start the sessions watchdog */ watchdog = g_thread_try_new("serial watchdog", &janus_serial_watchdog, NULL, &error); if(error != NULL) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Serial watchdog thread...\n", error->code, error->message ? error->message : "??"); return -1; } /* Launch the thread that will handle incoming messages */ handler_thread = g_thread_try_new("janus serial handler", janus_serial_handler, NULL, &error); if(error != NULL) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the serial handler thread...\n", error->code, error->message ? error->message : "??"); return -1; } // init part /* Open the file descriptor in non-blocking mode */ if(fd = open(portname,O_RDWR | O_NOCTTY | O_NONBLOCK)){ //printf("stream aperto\n"); JANUS_LOG(LOG_INFO, "stream aperto - Janus Serial\n"); }else{ //printf("errora nell'apertura dello stream\n"); JANUS_LOG(LOG_INFO, "errore nell'apertura dello stream\n"); return 0; } /* Set up the control structure */ struct termios toptions; /* Get currently set options for the tty */ tcgetattr(fd, &toptions); /* Set custom options */ /* 9600 baud */ cfsetispeed(&toptions, B9600); cfsetospeed(&toptions, B9600); /* 8 bits, no parity, no stop bits */ toptions.c_cflag &= ~PARENB; toptions.c_cflag &= ~CSTOPB; toptions.c_cflag &= ~CSIZE; toptions.c_cflag |= CS8; /* no hardware flow control */ toptions.c_cflag &= ~CRTSCTS; /* enable receiver, ignore status lines */ toptions.c_cflag |= CREAD | CLOCAL; /* disable input/output flow control, disable restart chars */ toptions.c_iflag &= ~(IXON | IXOFF | IXANY); /* disable canonical input, disable echo, disable visually erase chars, disable terminal-generated signals */ toptions.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG); /* disable output processing */ toptions.c_oflag &= ~OPOST; /* wait for 24 characters to come in before read returns */ toptions.c_cc[VMIN] = 12; /* no minimum time to wait before read returns */ toptions.c_cc[VTIME] = 0; /* commit the options */ tcsetattr(fd, TCSANOW, &toptions); /* Wait for the Arduino to reset */ usleep(1000*1000); /* Flush anything already in the serial buffer */ tcflush(fd, TCIFLUSH); write(fd,"k",1); JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_SERIAL_NAME); return 0; }
/* Plugin implementation */ int janus_streaming_init(janus_callbacks *callback, const char *config_path) { if(stopping) { /* Still stopping from before */ return -1; } if(callback == NULL || config_path == NULL) { /* Invalid arguments */ return -1; } /* Read configuration */ char filename[255]; sprintf(filename, "%s/%s.cfg", config_path, JANUS_STREAMING_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if(config != NULL) janus_config_print(config); mountpoints = g_hash_table_new(NULL, NULL); /* Parse configuration to populate the mountpoints */ if(config != NULL) { janus_config_category *cat = janus_config_get_categories(config); while(cat != NULL) { if(cat->name == NULL) { cat = cat->next; continue; } JANUS_LOG(LOG_VERB, "Adding stream '%s'\n", cat->name); janus_config_item *type = janus_config_get_item(cat, "type"); if(type == NULL || type->value == NULL) { JANUS_LOG(LOG_VERB, " -- Invalid type, skipping stream...\n"); cat = cat->next; continue; } if(!strcasecmp(type->value, "rtp")) { /* RTP live source (e.g., from gstreamer/ffmpeg/vlc/etc.) */ janus_config_item *id = janus_config_get_item(cat, "id"); janus_config_item *desc = janus_config_get_item(cat, "description"); janus_config_item *audio = janus_config_get_item(cat, "audio"); janus_config_item *video = janus_config_get_item(cat, "video"); janus_config_item *aport = janus_config_get_item(cat, "audioport"); janus_config_item *acodec = janus_config_get_item(cat, "audiopt"); janus_config_item *artpmap = janus_config_get_item(cat, "audiortpmap"); janus_config_item *vport = janus_config_get_item(cat, "videoport"); janus_config_item *vcodec = janus_config_get_item(cat, "videopt"); janus_config_item *vrtpmap = janus_config_get_item(cat, "videortpmap"); janus_streaming_mountpoint *live_rtp = calloc(1, sizeof(janus_streaming_mountpoint)); if(live_rtp == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); continue; } if(id == NULL || id->value == NULL) { JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream, missing mandatory information...\n"); cat = cat->next; continue; } gboolean doaudio = audio && audio->value && !strcasecmp(audio->value, "yes"); gboolean dovideo = video && video->value && !strcasecmp(video->value, "yes"); if(!doaudio && !dovideo) { JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream, no audio or video have to be streamed...\n"); g_free(live_rtp); cat = cat->next; continue; } if(doaudio && (aport == NULL || aport->value == NULL || acodec == NULL || acodec->value == NULL || artpmap == NULL || artpmap->value == NULL)) { JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream, missing mandatory information for audio...\n"); cat = cat->next; continue; } if(dovideo && (vport == NULL || vport->value == NULL || vcodec == NULL || vcodec->value == NULL || vrtpmap == NULL || vrtpmap->value == NULL)) { JANUS_LOG(LOG_ERR, "Can't add 'rtp' stream, missing mandatory information for video...\n"); cat = cat->next; continue; } JANUS_LOG(LOG_VERB, "Audio %s, Video %s\n", doaudio ? "enabled" : "NOT enabled", dovideo ? "enabled" : "NOT enabled"); live_rtp->name = g_strdup(cat->name); live_rtp->id = atoi(id->value); char *description = NULL; if(desc != NULL && desc->value != NULL) description = g_strdup(desc->value); else description = g_strdup(cat->name); live_rtp->description = description; live_rtp->active = FALSE; live_rtp->streaming_type = janus_streaming_type_live; live_rtp->streaming_source = janus_streaming_source_rtp; janus_streaming_rtp_source *live_rtp_source = calloc(1, sizeof(janus_streaming_rtp_source)); if(live_rtp->name == NULL || description == NULL || live_rtp_source == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); if(live_rtp->name) g_free(live_rtp->name); if(description) g_free(description); if(live_rtp_source); g_free(live_rtp_source); g_free(live_rtp); continue; } live_rtp_source->audio_port = doaudio ? atoi(aport->value) : -1; live_rtp_source->video_port = dovideo ? atoi(vport->value) : -1; live_rtp->source = live_rtp_source; live_rtp->codecs.audio_pt = doaudio ? atoi(acodec->value) : -1; live_rtp->codecs.audio_rtpmap = doaudio ? g_strdup(artpmap->value) : NULL; live_rtp->codecs.video_pt = dovideo ? atoi(vcodec->value) : -1; live_rtp->codecs.video_rtpmap = dovideo ? g_strdup(vrtpmap->value) : NULL; live_rtp->listeners = NULL; g_hash_table_insert(mountpoints, GINT_TO_POINTER(live_rtp->id), live_rtp); g_thread_new(live_rtp->name, &janus_streaming_relay_thread, live_rtp); } else if(!strcasecmp(type->value, "live")) { /* a-Law file live source */ janus_config_item *id = janus_config_get_item(cat, "id"); janus_config_item *desc = janus_config_get_item(cat, "description"); janus_config_item *file = janus_config_get_item(cat, "filename"); janus_config_item *audio = janus_config_get_item(cat, "audio"); janus_config_item *video = janus_config_get_item(cat, "video"); if(id == NULL || id->value == NULL || file == NULL || file->value == NULL) { JANUS_LOG(LOG_ERR, "Can't add 'live' stream, missing mandatory information...\n"); cat = cat->next; continue; } gboolean doaudio = audio && audio->value && !strcasecmp(audio->value, "yes"); gboolean dovideo = video && video->value && !strcasecmp(video->value, "yes"); /* TODO We should support something more than raw a-Law and mu-Law streams... */ if(!doaudio || dovideo) { JANUS_LOG(LOG_ERR, "Can't add 'live' stream, we only support audio file streaming right now...\n"); cat = cat->next; continue; } if(!strstr(file->value, ".alaw") && !strstr(file->value, ".mulaw")) { JANUS_LOG(LOG_ERR, "Can't add 'ondemand' stream, unsupported format (we only support raw mu-Law and a-Law files right now)\n"); cat = cat->next; continue; } janus_streaming_mountpoint *live_file = calloc(1, sizeof(janus_streaming_mountpoint)); if(live_file == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); continue; } live_file->name = g_strdup(cat->name); live_file->id = atoi(id->value); char *description = NULL; if(desc != NULL && desc->value != NULL) description = g_strdup(desc->value); else description = g_strdup(cat->name); live_file->description = description; live_file->active = FALSE; live_file->streaming_type = janus_streaming_type_live; live_file->streaming_source = janus_streaming_source_file; janus_streaming_file_source *live_file_source = calloc(1, sizeof(janus_streaming_file_source)); if(live_file->name == NULL || description == NULL || live_file_source == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); if(live_file->name) g_free(live_file->name); if(description) g_free(description); if(live_file_source); g_free(live_file_source); g_free(live_file); continue; } live_file_source->filename = g_strdup(file->value); live_file->source = live_file_source; live_file->codecs.audio_pt = strstr(file->value, ".alaw") ? 8 : 0; live_file->codecs.audio_rtpmap = strstr(file->value, ".alaw") ? "PCMA/8000" : "PCMU/8000"; live_file->codecs.video_pt = -1; /* FIXME We don't support video for this type yet */ live_file->codecs.video_rtpmap = NULL; live_file->listeners = NULL; g_hash_table_insert(mountpoints, GINT_TO_POINTER(live_file->id), live_file); g_thread_new(live_file->name, &janus_streaming_filesource_thread, live_file); } else if(!strcasecmp(type->value, "ondemand")) { /* mu-Law file on demand source */ janus_config_item *id = janus_config_get_item(cat, "id"); janus_config_item *desc = janus_config_get_item(cat, "description"); janus_config_item *file = janus_config_get_item(cat, "filename"); janus_config_item *audio = janus_config_get_item(cat, "audio"); janus_config_item *video = janus_config_get_item(cat, "video"); if(id == NULL || id->value == NULL || file == NULL || file->value == NULL) { JANUS_LOG(LOG_ERR, "Can't add 'ondemand' stream, missing mandatory information...\n"); cat = cat->next; continue; } gboolean doaudio = audio && audio->value && !strcasecmp(audio->value, "yes"); gboolean dovideo = video && video->value && !strcasecmp(video->value, "yes"); /* TODO We should support something more than raw a-Law and mu-Law streams... */ if(!doaudio || dovideo) { JANUS_LOG(LOG_ERR, "Can't add 'ondemand' stream, we only support audio file streaming right now...\n"); cat = cat->next; continue; } if(!strstr(file->value, ".alaw") && !strstr(file->value, ".mulaw")) { JANUS_LOG(LOG_ERR, "Can't add 'ondemand' stream, unsupported format (we only support raw mu-Law and a-Law files right now)\n"); cat = cat->next; continue; } janus_streaming_mountpoint *ondemand_file = calloc(1, sizeof(janus_streaming_mountpoint)); if(ondemand_file == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); continue; } ondemand_file->name = g_strdup(cat->name); ondemand_file->id = atoi(id->value); char *description = NULL; if(desc != NULL && desc->value != NULL) description = g_strdup(desc->value); else description = g_strdup(cat->name); ondemand_file->description = description; ondemand_file->active = FALSE; ondemand_file->streaming_type = janus_streaming_type_on_demand; ondemand_file->streaming_source = janus_streaming_source_file; janus_streaming_file_source *ondemand_file_source = calloc(1, sizeof(janus_streaming_file_source)); if(ondemand_file->name == NULL || description == NULL || ondemand_file_source == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); if(ondemand_file->name) g_free(ondemand_file->name); if(description) g_free(description); if(ondemand_file_source); g_free(ondemand_file_source); g_free(ondemand_file); continue; } ondemand_file_source->filename = g_strdup(file->value); ondemand_file->source = ondemand_file_source; ondemand_file->codecs.audio_pt = strstr(file->value, ".alaw") ? 8 : 0; ondemand_file->codecs.audio_rtpmap = strstr(file->value, ".alaw") ? "PCMA/8000" : "PCMU/8000"; ondemand_file->codecs.video_pt = -1; /* FIXME We don't support video for this type yet */ ondemand_file->codecs.video_rtpmap = NULL; ondemand_file->listeners = NULL; g_hash_table_insert(mountpoints, GINT_TO_POINTER(ondemand_file->id), ondemand_file); } cat = cat->next; } /* Done */ janus_config_destroy(config); config = NULL; } /* Show available mountpoints */ GList *mountpoints_list = g_hash_table_get_values(mountpoints); GList *m = mountpoints_list; while(m) { janus_streaming_mountpoint *mp = (janus_streaming_mountpoint *)m->data; JANUS_LOG(LOG_VERB, " ::: [%"SCNu64"][%s] %s (%s, %s)\n", mp->id, mp->name, mp->description, mp->streaming_type == janus_streaming_type_live ? "live" : "on demand", mp->streaming_source == janus_streaming_source_rtp ? "RTP source" : "file source"); m = m->next; } g_list_free(mountpoints_list); sessions = g_hash_table_new(NULL, NULL); janus_mutex_init(&sessions_mutex); messages = g_queue_new(); /* This is the callback we'll need to invoke to contact the gateway */ gateway = callback; initialized = 1; /* Launch the thread that will handle incoming messages */ GError *error = NULL; handler_thread = g_thread_try_new("janus streaming handler", janus_streaming_handler, NULL, &error); if(error != NULL) { initialized = 0; /* Something went wrong... */ JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch thread...\n", error->code, error->message ? error->message : "??"); return -1; } JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_STREAMING_NAME); return 0; }
int janus_websockets_send_message(void *transport, void *request_id, gboolean admin, json_t *message) { if(message == NULL) return -1; if(transport == NULL) { json_decref(message); return -1; } /* Make sure this is not related to a closed /freed WebSocket session */ janus_mutex_lock(&old_wss_mutex); janus_websockets_client *client = (janus_websockets_client *)transport; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(g_list_find(old_wss, client) != NULL || !client->wsi) { #else if(g_list_find(old_wss, client) != NULL || !client->context || !client->wsi) { #endif json_decref(message); message = NULL; transport = NULL; janus_mutex_unlock(&old_wss_mutex); return -1; } janus_mutex_lock(&client->mutex); /* Convert to string and enqueue */ char *payload = json_dumps(message, json_format); g_async_queue_push(client->messages, payload); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(client->wsi); #else libwebsocket_callback_on_writable(client->context, client->wsi); #endif janus_mutex_unlock(&client->mutex); janus_mutex_unlock(&old_wss_mutex); json_decref(message); return 0; } void janus_websockets_session_created(void *transport, guint64 session_id) { /* We don't care */ } void janus_websockets_session_over(void *transport, guint64 session_id, gboolean timeout) { if(transport == NULL || !timeout) return; /* We only care if it's a timeout: if so, close the connection */ janus_websockets_client *client = (janus_websockets_client *)transport; /* Make sure this is not related to a closed WebSocket session */ janus_mutex_lock(&old_wss_mutex); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(g_list_find(old_wss, client) == NULL && client->wsi){ #else if(g_list_find(old_wss, client) == NULL && client->context && client->wsi){ #endif janus_mutex_lock(&client->mutex); client->session_timeout = 1; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(client->wsi); #else libwebsocket_callback_on_writable(client->context, client->wsi); #endif janus_mutex_unlock(&client->mutex); } janus_mutex_unlock(&old_wss_mutex); } /* Thread */ void *janus_websockets_thread(void *data) { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws_context *service = (struct lws_context *)data; #else struct libwebsocket_context *service = (struct libwebsocket_context *)data; #endif if(service == NULL) { JANUS_LOG(LOG_ERR, "Invalid service\n"); return NULL; } const char *type = NULL; if(service == wss) type = "WebSocket (Janus API)"; else if(service == swss) type = "Secure WebSocket (Janus API)"; else if(service == admin_wss) type = "WebSocket (Admin API)"; else if(service == admin_swss) type = "Secure WebSocket (Admin API)"; JANUS_LOG(LOG_INFO, "%s thread started\n", type); while(g_atomic_int_get(&initialized) && !g_atomic_int_get(&stopping)) { /* libwebsockets is single thread, we cycle through events here */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_service(service, 50); #else libwebsocket_service(service, 50); #endif } /* Get rid of the WebSockets server */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_cancel_service(service); #else libwebsocket_cancel_service(service); #endif /* Done */ JANUS_LOG(LOG_INFO, "%s thread ended\n", type); return NULL; } /* WebSockets */ static int janus_websockets_callback_http( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { /* This endpoint cannot be used for HTTP */ switch(reason) { case LWS_CALLBACK_HTTP: JANUS_LOG(LOG_VERB, "Rejecting incoming HTTP request on WebSockets endpoint\n"); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_return_http_status(wsi, 403, NULL); #else libwebsockets_return_http_status(this, wsi, 403, NULL); #endif /* Close and free connection */ return -1; case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: if (!in) { JANUS_LOG(LOG_VERB, "Rejecting incoming HTTP request on WebSockets endpoint: no sub-protocol specified\n"); return -1; } break; default: break; } return 0; } static int janus_websockets_callback_https( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { /* We just forward the event to the HTTP handler */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_callback_http(wsi, reason, user, in, len); #else return janus_websockets_callback_http(this, wsi, reason, user, in, len); #endif } /* This callback handles Janus API requests */ static int janus_websockets_common_callback( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len, gboolean admin) { const char *log_prefix = admin ? "AdminWSS" : "WSS"; janus_websockets_client *ws_client = (janus_websockets_client *)user; switch(reason) { case LWS_CALLBACK_ESTABLISHED: { /* Is there any filtering we should apply? */ char name[256], ip[256]; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, 256, ip, 256); #else libwebsockets_get_peer_addresses(this, wsi, libwebsocket_get_socket_fd(wsi), name, 256, ip, 256); #endif JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection opened from %s by %s\n", log_prefix, wsi, ip, name); if(!janus_websockets_is_allowed(ip, admin)) { JANUS_LOG(LOG_ERR, "[%s-%p] IP %s is unauthorized to connect to the WebSockets %s API interface\n", log_prefix, wsi, ip, admin ? "Admin" : "Janus"); /* Close the connection */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(wsi); #else libwebsocket_callback_on_writable(this, wsi); #endif return -1; } JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection accepted\n", log_prefix, wsi); if(ws_client == NULL) { JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } /* Clean the old sessions list, in case this pointer was used before */ janus_mutex_lock(&old_wss_mutex); if(g_list_find(old_wss, ws_client) != NULL) old_wss = g_list_remove(old_wss, ws_client); janus_mutex_unlock(&old_wss_mutex); /* Prepare the session */ #ifndef HAVE_LIBWEBSOCKETS_NEWAPI ws_client->context = this; #endif ws_client->wsi = wsi; ws_client->messages = g_async_queue_new(); ws_client->buffer = NULL; ws_client->buflen = 0; ws_client->bufpending = 0; ws_client->bufoffset = 0; ws_client->session_timeout = 0; ws_client->destroy = 0; janus_mutex_init(&ws_client->mutex); /* Let us know when the WebSocket channel becomes writeable */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(wsi); #else libwebsocket_callback_on_writable(this, wsi); #endif JANUS_LOG(LOG_VERB, "[%s-%p] -- Ready to be used!\n", log_prefix, wsi); /* Notify handlers about this new transport */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "event", json_string("connected")); json_object_set_new(info, "admin_api", admin ? json_true() : json_false()); json_object_set_new(info, "ip", json_string(ip)); gateway->notify_event(&janus_websockets_transport, ws_client, info); } return 0; } case LWS_CALLBACK_RECEIVE: { JANUS_LOG(LOG_HUGE, "[%s-%p] Got %zu bytes:\n", log_prefix, wsi, len); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(ws_client == NULL || ws_client->wsi == NULL) { #else if(ws_client == NULL || ws_client->context == NULL || ws_client->wsi == NULL) { #endif JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } /* Is this a new message, or part of a fragmented one? */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI const size_t remaining = lws_remaining_packet_payload(wsi); #else const size_t remaining = libwebsockets_remaining_packet_payload(wsi); #endif if(ws_client->incoming == NULL) { JANUS_LOG(LOG_HUGE, "[%s-%p] First fragment: %zu bytes, %zu remaining\n", log_prefix, wsi, len, remaining); ws_client->incoming = g_malloc0(len+1); memcpy(ws_client->incoming, in, len); ws_client->incoming[len] = '\0'; JANUS_LOG(LOG_HUGE, "%s\n", ws_client->incoming); } else { size_t offset = strlen(ws_client->incoming); JANUS_LOG(LOG_HUGE, "[%s-%p] Appending fragment: offset %zu, %zu bytes, %zu remaining\n", log_prefix, wsi, offset, len, remaining); ws_client->incoming = g_realloc(ws_client->incoming, offset+len+1); memcpy(ws_client->incoming+offset, in, len); ws_client->incoming[offset+len] = '\0'; JANUS_LOG(LOG_HUGE, "%s\n", ws_client->incoming+offset); } #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(remaining > 0 || !lws_is_final_fragment(wsi)) { #else if(remaining > 0 || !libwebsocket_is_final_fragment(wsi)) { #endif /* Still waiting for some more fragments */ JANUS_LOG(LOG_HUGE, "[%s-%p] Waiting for more fragments\n", log_prefix, wsi); return 0; } JANUS_LOG(LOG_HUGE, "[%s-%p] Done, parsing message: %zu bytes\n", log_prefix, wsi, strlen(ws_client->incoming)); /* If we got here, the message is complete: parse the JSON payload */ json_error_t error; json_t *root = json_loads(ws_client->incoming, 0, &error); g_free(ws_client->incoming); ws_client->incoming = NULL; /* Notify the core, passing both the object and, since it may be needed, the error */ gateway->incoming_request(&janus_websockets_transport, ws_client, NULL, admin, root, &error); return 0; } case LWS_CALLBACK_SERVER_WRITEABLE: { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI if(ws_client == NULL || ws_client->wsi == NULL) { #else if(ws_client == NULL || ws_client->context == NULL || ws_client->wsi == NULL) { #endif JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi); return -1; } if(!ws_client->destroy && !g_atomic_int_get(&stopping)) { janus_mutex_lock(&ws_client->mutex); /* Check if we have a pending/partial write to complete first */ if(ws_client->buffer && ws_client->bufpending > 0 && ws_client->bufoffset > 0 && !ws_client->destroy && !g_atomic_int_get(&stopping)) { JANUS_LOG(LOG_HUGE, "[%s-%p] Completing pending WebSocket write (still need to write last %d bytes)...\n", log_prefix, wsi, ws_client->bufpending); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI int sent = lws_write(wsi, ws_client->buffer + ws_client->bufoffset, ws_client->bufpending, LWS_WRITE_TEXT); #else int sent = libwebsocket_write(wsi, ws_client->buffer + ws_client->bufoffset, ws_client->bufpending, LWS_WRITE_TEXT); #endif JANUS_LOG(LOG_HUGE, "[%s-%p] -- Sent %d/%d bytes\n", log_prefix, wsi, sent, ws_client->bufpending); if(sent > -1 && sent < ws_client->bufpending) { /* We still couldn't send everything that was left, we'll try and complete this in the next round */ ws_client->bufpending -= sent; ws_client->bufoffset += sent; } else { /* Clear the pending/partial write queue */ ws_client->bufpending = 0; ws_client->bufoffset = 0; } /* Done for this round, check the next response/notification later */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(wsi); #else libwebsocket_callback_on_writable(this, wsi); #endif janus_mutex_unlock(&ws_client->mutex); return 0; } /* Shoot all the pending messages */ char *response = g_async_queue_try_pop(ws_client->messages); if(response && !ws_client->destroy && !g_atomic_int_get(&stopping)) { /* Gotcha! */ int buflen = LWS_SEND_BUFFER_PRE_PADDING + strlen(response) + LWS_SEND_BUFFER_POST_PADDING; if(ws_client->buffer == NULL) { /* Let's allocate a shared buffer */ JANUS_LOG(LOG_HUGE, "[%s-%p] Allocating %d bytes (response is %zu bytes)\n", log_prefix, wsi, buflen, strlen(response)); ws_client->buflen = buflen; ws_client->buffer = g_malloc0(buflen); } else if(buflen > ws_client->buflen) { /* We need a larger shared buffer */ JANUS_LOG(LOG_HUGE, "[%s-%p] Re-allocating to %d bytes (was %d, response is %zu bytes)\n", log_prefix, wsi, buflen, ws_client->buflen, strlen(response)); ws_client->buflen = buflen; ws_client->buffer = g_realloc(ws_client->buffer, buflen); } memcpy(ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, response, strlen(response)); JANUS_LOG(LOG_HUGE, "[%s-%p] Sending WebSocket message (%zu bytes)...\n", log_prefix, wsi, strlen(response)); #ifdef HAVE_LIBWEBSOCKETS_NEWAPI int sent = lws_write(wsi, ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, strlen(response), LWS_WRITE_TEXT); #else int sent = libwebsocket_write(wsi, ws_client->buffer + LWS_SEND_BUFFER_PRE_PADDING, strlen(response), LWS_WRITE_TEXT); #endif JANUS_LOG(LOG_HUGE, "[%s-%p] -- Sent %d/%zu bytes\n", log_prefix, wsi, sent, strlen(response)); if(sent > -1 && sent < (int)strlen(response)) { /* We couldn't send everything in a single write, we'll complete this in the next round */ ws_client->bufpending = strlen(response) - sent; ws_client->bufoffset = LWS_SEND_BUFFER_PRE_PADDING + sent; JANUS_LOG(LOG_HUGE, "[%s-%p] -- Couldn't write all bytes (%d missing), setting offset %d\n", log_prefix, wsi, ws_client->bufpending, ws_client->bufoffset); } /* We can get rid of the message */ free(response); /* Done for this round, check the next response/notification later */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI lws_callback_on_writable(wsi); #else libwebsocket_callback_on_writable(this, wsi); #endif janus_mutex_unlock(&ws_client->mutex); return 0; } janus_mutex_unlock(&ws_client->mutex); } return 0; } case LWS_CALLBACK_CLOSED: { JANUS_LOG(LOG_VERB, "[%s-%p] WS connection down, closing\n", log_prefix, wsi); janus_websockets_destroy_client(ws_client, wsi, log_prefix); JANUS_LOG(LOG_VERB, "[%s-%p] -- closed\n", log_prefix, wsi); return 0; } case LWS_CALLBACK_WSI_DESTROY: { JANUS_LOG(LOG_VERB, "[%s-%p] WS connection down, destroying\n", log_prefix, wsi); janus_websockets_destroy_client(ws_client, wsi, log_prefix); JANUS_LOG(LOG_VERB, "[%s-%p] -- destroyed\n", log_prefix, wsi); return 0; } default: if(wsi != NULL) { JANUS_LOG(LOG_HUGE, "[%s-%p] %d (%s)\n", log_prefix, wsi, reason, janus_websockets_reason_string(reason)); } else { JANUS_LOG(LOG_HUGE, "[%s] %d (%s)\n", log_prefix, reason, janus_websockets_reason_string(reason)); } break; } return 0; } /* This callback handles Janus API requests */ static int janus_websockets_callback( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_common_callback(wsi, reason, user, in, len, FALSE); #else return janus_websockets_common_callback(this, wsi, reason, user, in, len, FALSE); #endif } static int janus_websockets_callback_secure( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { /* We just forward the event to the Janus API handler */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_callback(wsi, reason, user, in, len); #else return janus_websockets_callback(this, wsi, reason, user, in, len); #endif } /* This callback handles Admin API requests */ static int janus_websockets_admin_callback( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_common_callback(wsi, reason, user, in, len, TRUE); #else return janus_websockets_common_callback(this, wsi, reason, user, in, len, TRUE); #endif } static int janus_websockets_admin_callback_secure( #ifdef HAVE_LIBWEBSOCKETS_NEWAPI struct lws *wsi, enum lws_callback_reasons reason, #else struct libwebsocket_context *this, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, #endif void *user, void *in, size_t len) { /* We just forward the event to the Admin API handler */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI return janus_websockets_admin_callback(wsi, reason, user, in, len); #else return janus_websockets_admin_callback(this, wsi, reason, user, in, len); #endif }
/* Transport implementation */ int janus_websockets_init(janus_transport_callbacks *callback, const char *config_path) { if(g_atomic_int_get(&stopping)) { /* Still stopping from before */ return -1; } if(callback == NULL || config_path == NULL) { /* Invalid arguments */ return -1; } /* This is the callback we'll need to invoke to contact the gateway */ gateway = callback; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI JANUS_LOG(LOG_INFO, "libwebsockets >= 1.6 available, using new API\n"); #else JANUS_LOG(LOG_INFO, "libwebsockets < 1.6 available, using old API\n"); #endif /* Read configuration */ char filename[255]; g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_WEBSOCKETS_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if(config != NULL) { janus_config_print(config); /* Handle configuration */ janus_config_item *item = janus_config_get_item_drilldown(config, "general", "json"); if(item && item->value) { /* Check how we need to format/serialize the JSON output */ if(!strcasecmp(item->value, "indented")) { /* Default: indented, we use three spaces for that */ json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; } else if(!strcasecmp(item->value, "plain")) { /* Not indented and no new lines, but still readable */ json_format = JSON_INDENT(0) | JSON_PRESERVE_ORDER; } else if(!strcasecmp(item->value, "compact")) { /* Compact, so no spaces between separators */ json_format = JSON_COMPACT | JSON_PRESERVE_ORDER; } else { JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', using default (indented)\n", item->value); json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; } } /* Check if we need to send events to handlers */ janus_config_item *events = janus_config_get_item_drilldown(config, "general", "events"); if(events != NULL && events->value != NULL) notify_events = janus_is_true(events->value); if(!notify_events && callback->events_is_enabled()) { JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_WEBSOCKETS_NAME); } item = janus_config_get_item_drilldown(config, "general", "ws_logging"); if(item && item->value) { ws_log_level = atoi(item->value); if(ws_log_level < 0) ws_log_level = 0; } JANUS_LOG(LOG_VERB, "libwebsockets logging: %d\n", ws_log_level); lws_set_log_level(ws_log_level, NULL); old_wss = NULL; janus_mutex_init(&old_wss_mutex); /* Any ACL for either the Janus or Admin API? */ item = janus_config_get_item_drilldown(config, "general", "ws_acl"); if(item && item->value) { gchar **list = g_strsplit(item->value, ",", -1); gchar *index = list[0]; if(index != NULL) { int i=0; while(index != NULL) { if(strlen(index) > 0) { JANUS_LOG(LOG_INFO, "Adding '%s' to the Janus API allowed list...\n", index); janus_websockets_allow_address(g_strdup(index), FALSE); } i++; index = list[i]; } } g_strfreev(list); list = NULL; } item = janus_config_get_item_drilldown(config, "admin", "admin_ws_acl"); if(item && item->value) { gchar **list = g_strsplit(item->value, ",", -1); gchar *index = list[0]; if(index != NULL) { int i=0; while(index != NULL) { if(strlen(index) > 0) { JANUS_LOG(LOG_INFO, "Adding '%s' to the Admin/monitor allowed list...\n", index); janus_websockets_allow_address(g_strdup(index), TRUE); } i++; index = list[i]; } } g_strfreev(list); list = NULL; } /* Setup the Janus API WebSockets server(s) */ item = janus_config_get_item_drilldown(config, "general", "ws"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "WebSockets server disabled\n"); } else { int wsport = 8188; item = janus_config_get_item_drilldown(config, "general", "ws_port"); if(item && item->value) wsport = atoi(item->value); char *interface = NULL; item = janus_config_get_item_drilldown(config, "general", "ws_interface"); if(item && item->value) interface = (char *)item->value; char *ip = NULL; item = janus_config_get_item_drilldown(config, "general", "ws_ip"); if(item && item->value) { ip = (char *)item->value; char *iface = janus_websockets_get_interface_name(ip); if(iface == NULL) { JANUS_LOG(LOG_WARN, "No interface associated with %s? Falling back to no interface...\n", ip); } ip = iface; } /* Prepare context */ struct lws_context_creation_info info; memset(&info, 0, sizeof info); info.port = wsport; info.iface = ip ? ip : interface; info.protocols = wss_protocols; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI info.extensions = NULL; #else info.extensions = libwebsocket_get_internal_extensions(); #endif info.ssl_cert_filepath = NULL; info.ssl_private_key_filepath = NULL; info.gid = -1; info.uid = -1; info.options = 0; /* Create the WebSocket context */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI wss = lws_create_context(&info); #else wss = libwebsocket_create_context(&info); #endif if(wss == NULL) { JANUS_LOG(LOG_FATAL, "Error initializing libwebsockets...\n"); } else { JANUS_LOG(LOG_INFO, "WebSockets server started (port %d)...\n", wsport); } g_free(ip); } item = janus_config_get_item_drilldown(config, "general", "wss"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "Secure WebSockets server disabled\n"); } else { int wsport = 8989; item = janus_config_get_item_drilldown(config, "general", "wss_port"); if(item && item->value) wsport = atoi(item->value); char *interface = NULL; item = janus_config_get_item_drilldown(config, "general", "wss_interface"); if(item && item->value) interface = (char *)item->value; char *ip = NULL; item = janus_config_get_item_drilldown(config, "general", "wss_ip"); if(item && item->value) { ip = (char *)item->value; char *iface = janus_websockets_get_interface_name(ip); if(iface == NULL) { JANUS_LOG(LOG_WARN, "No interface associated with %s? Falling back to no interface...\n", ip); } ip = iface; } item = janus_config_get_item_drilldown(config, "certificates", "cert_pem"); if(!item || !item->value) { JANUS_LOG(LOG_FATAL, "Missing certificate/key path\n"); } else { char *server_pem = (char *)item->value; char *server_key = (char *)item->value; item = janus_config_get_item_drilldown(config, "certificates", "cert_key"); if(item && item->value) server_key = (char *)item->value; JANUS_LOG(LOG_VERB, "Using certificates:\n\t%s\n\t%s\n", server_pem, server_key); /* Prepare secure context */ struct lws_context_creation_info info; memset(&info, 0, sizeof info); info.port = wsport; info.iface = ip ? ip : interface; info.protocols = swss_protocols; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI info.extensions = NULL; #else info.extensions = libwebsocket_get_internal_extensions(); #endif info.ssl_cert_filepath = server_pem; info.ssl_private_key_filepath = server_key; info.gid = -1; info.uid = -1; #if LWS_LIBRARY_VERSION_MAJOR >= 2 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; #else info.options = 0; #endif /* Create the secure WebSocket context */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI swss = lws_create_context(&info); #else swss = libwebsocket_create_context(&info); #endif if(swss == NULL) { JANUS_LOG(LOG_FATAL, "Error initializing libwebsockets...\n"); } else { JANUS_LOG(LOG_INFO, "Secure WebSockets server started (port %d)...\n", wsport); } g_free(ip); } } /* Do the same for the Admin API, if enabled */ item = janus_config_get_item_drilldown(config, "admin", "admin_ws"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "Admin WebSockets server disabled\n"); } else { int wsport = 7188; item = janus_config_get_item_drilldown(config, "admin", "admin_ws_port"); if(item && item->value) wsport = atoi(item->value); char *interface = NULL; item = janus_config_get_item_drilldown(config, "admin", "admin_ws_interface"); if(item && item->value) interface = (char *)item->value; char *ip = NULL; item = janus_config_get_item_drilldown(config, "admin", "admin_ws_ip"); if(item && item->value) { ip = (char *)item->value; char *iface = janus_websockets_get_interface_name(ip); if(iface == NULL) { JANUS_LOG(LOG_WARN, "No interface associated with %s? Falling back to no interface...\n", ip); } ip = iface; } /* Prepare context */ struct lws_context_creation_info info; memset(&info, 0, sizeof info); info.port = wsport; info.iface = ip ? ip : interface; info.protocols = admin_wss_protocols; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI info.extensions = NULL; #else info.extensions = libwebsocket_get_internal_extensions(); #endif info.ssl_cert_filepath = NULL; info.ssl_private_key_filepath = NULL; info.gid = -1; info.uid = -1; info.options = 0; /* Create the WebSocket context */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI admin_wss = lws_create_context(&info); #else admin_wss = libwebsocket_create_context(&info); #endif if(admin_wss == NULL) { JANUS_LOG(LOG_FATAL, "Error initializing libwebsockets...\n"); } else { JANUS_LOG(LOG_INFO, "Admin WebSockets server started (port %d)...\n", wsport); } g_free(ip); } item = janus_config_get_item_drilldown(config, "admin", "admin_wss"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "Secure Admin WebSockets server disabled\n"); } else { int wsport = 7989; item = janus_config_get_item_drilldown(config, "admin", "admin_wss_port"); if(item && item->value) wsport = atoi(item->value); char *interface = NULL; item = janus_config_get_item_drilldown(config, "admin", "admin_wss_interface"); if(item && item->value) interface = (char *)item->value; char *ip = NULL; item = janus_config_get_item_drilldown(config, "admin", "admin_wss_ip"); if(item && item->value) { ip = (char *)item->value; char *iface = janus_websockets_get_interface_name(ip); if(iface == NULL) { JANUS_LOG(LOG_WARN, "No interface associated with %s? Falling back to no interface...\n", ip); } ip = iface; } item = janus_config_get_item_drilldown(config, "certificates", "cert_pem"); if(!item || !item->value) { JANUS_LOG(LOG_FATAL, "Missing certificate/key path\n"); } else { char *server_pem = (char *)item->value; char *server_key = (char *)item->value; item = janus_config_get_item_drilldown(config, "certificates", "cert_key"); if(item && item->value) server_key = (char *)item->value; JANUS_LOG(LOG_VERB, "Using certificates:\n\t%s\n\t%s\n", server_pem, server_key); /* Prepare secure context */ struct lws_context_creation_info info; memset(&info, 0, sizeof info); info.port = wsport; info.iface = ip ? ip : interface; info.protocols = admin_swss_protocols; #ifdef HAVE_LIBWEBSOCKETS_NEWAPI info.extensions = NULL; #else info.extensions = libwebsocket_get_internal_extensions(); #endif info.ssl_cert_filepath = server_pem; info.ssl_private_key_filepath = server_key; info.gid = -1; info.uid = -1; #if LWS_LIBRARY_VERSION_MAJOR >= 2 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; #else info.options = 0; #endif /* Create the secure WebSocket context */ #ifdef HAVE_LIBWEBSOCKETS_NEWAPI admin_swss = lws_create_context(&info); #else admin_swss = libwebsocket_create_context(&info); #endif if(admin_swss == NULL) { JANUS_LOG(LOG_FATAL, "Error initializing libwebsockets...\n"); } else { JANUS_LOG(LOG_INFO, "Secure Admin WebSockets server started (port %d)...\n", wsport); } g_free(ip); } } } janus_config_destroy(config); config = NULL; if(!wss && !swss && !admin_wss && !admin_swss) { JANUS_LOG(LOG_FATAL, "No WebSockets server started, giving up...\n"); return -1; /* No point in keeping the plugin loaded */ } wss_janus_api_enabled = wss || swss; wss_admin_api_enabled = admin_wss || admin_swss; GError *error = NULL; /* Start the WebSocket service threads */ if(wss != NULL) { wss_thread = g_thread_try_new("ws thread", &janus_websockets_thread, wss, &error); if(!wss_thread) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the WebSockets thread...\n", error->code, error->message ? error->message : "??"); return -1; } } if(swss != NULL) { swss_thread = g_thread_try_new("sws thread", &janus_websockets_thread, swss, &error); if(!swss_thread) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Secure WebSockets thread...\n", error->code, error->message ? error->message : "??"); return -1; } } if(admin_wss != NULL) { admin_wss_thread = g_thread_try_new("admin ws thread", &janus_websockets_thread, admin_wss, &error); if(!admin_wss_thread) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Admin WebSockets thread...\n", error->code, error->message ? error->message : "??"); return -1; } } if(admin_swss != NULL) { admin_swss_thread = g_thread_try_new("admin sws thread", &janus_websockets_thread, admin_swss, &error); if(!admin_swss_thread) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Secure Admin WebSockets thread...\n", error->code, error->message ? error->message : "??"); return -1; } } /* Done */ g_atomic_int_set(&initialized, 1); JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_WEBSOCKETS_NAME); return 0; }
/* DTLS-SRTP initialization */ gint janus_dtls_srtp_init(const char *server_pem, const char *server_key, const char *password) { const char *crypto_lib = NULL; #if JANUS_USE_OPENSSL_PRE_1_1_API #if defined(LIBRESSL_VERSION_NUMBER) crypto_lib = "LibreSSL"; #else crypto_lib = "OpenSSL pre-1.1.0"; #endif /* First of all make OpenSSL thread safe (see note above on issue #316) */ janus_dtls_locks = g_malloc0(sizeof(*janus_dtls_locks) * CRYPTO_num_locks()); int l=0; for(l = 0; l < CRYPTO_num_locks(); l++) { janus_mutex_init(&janus_dtls_locks[l]); } CRYPTO_THREADID_set_callback(janus_dtls_cb_openssl_threadid); CRYPTO_set_locking_callback(janus_dtls_cb_openssl_lock); #else crypto_lib = "OpenSSL >= 1.1.0"; #endif #ifdef HAVE_BORINGSSL crypto_lib = "BoringSSL"; #endif JANUS_LOG(LOG_INFO, "Crypto: %s\n", crypto_lib); #ifndef HAVE_SRTP_AESGCM JANUS_LOG(LOG_WARN, "The libsrtp installation does not support AES-GCM profiles\n"); #endif /* Go on and create the DTLS context */ #if JANUS_USE_OPENSSL_PRE_1_1_API ssl_ctx = SSL_CTX_new(DTLSv1_method()); #else ssl_ctx = SSL_CTX_new(DTLS_method()); #endif if(!ssl_ctx) { JANUS_LOG(LOG_FATAL, "Ops, error creating DTLS context?\n"); return -1; } SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, janus_dtls_verify_callback); SSL_CTX_set_tlsext_use_srtp(ssl_ctx, #ifdef HAVE_SRTP_AESGCM "SRTP_AEAD_AES_256_GCM:SRTP_AEAD_AES_128_GCM:SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32"); #else "SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32"); #endif if(!server_pem && !server_key) { JANUS_LOG(LOG_WARN, "No cert/key specified, autogenerating some...\n"); if(janus_dtls_generate_keys(&ssl_cert, &ssl_key) != 0) { JANUS_LOG(LOG_FATAL, "Error generating DTLS key/certificate\n"); return -2; } } else if(!server_pem || !server_key) { JANUS_LOG(LOG_FATAL, "DTLS certificate and key must be specified\n"); return -2; } else if(janus_dtls_load_keys(server_pem, server_key, password, &ssl_cert, &ssl_key) != 0) { return -3; } if(!SSL_CTX_use_certificate(ssl_ctx, ssl_cert)) { JANUS_LOG(LOG_FATAL, "Certificate error (%s)\n", ERR_reason_error_string(ERR_get_error())); return -4; } if(!SSL_CTX_use_PrivateKey(ssl_ctx, ssl_key)) { JANUS_LOG(LOG_FATAL, "Certificate key error (%s)\n", ERR_reason_error_string(ERR_get_error())); return -5; } if(!SSL_CTX_check_private_key(ssl_ctx)) { JANUS_LOG(LOG_FATAL, "Certificate check error (%s)\n", ERR_reason_error_string(ERR_get_error())); return -6; } SSL_CTX_set_read_ahead(ssl_ctx,1); unsigned int size; unsigned char fingerprint[EVP_MAX_MD_SIZE]; if(X509_digest(ssl_cert, EVP_sha256(), (unsigned char *)fingerprint, &size) == 0) { JANUS_LOG(LOG_FATAL, "Error converting X509 structure (%s)\n", ERR_reason_error_string(ERR_get_error())); return -7; } char *lfp = (char *)&local_fingerprint; unsigned int i = 0; for(i = 0; i < size; i++) { g_snprintf(lfp, 4, "%.2X:", fingerprint[i]); lfp += 3; } *(lfp-1) = 0; JANUS_LOG(LOG_INFO, "Fingerprint of our certificate: %s\n", local_fingerprint); SSL_CTX_set_cipher_list(ssl_ctx, DTLS_CIPHERS); if(janus_dtls_bio_filter_init() < 0) { JANUS_LOG(LOG_FATAL, "Error initializing BIO filter\n"); return -8; } /* Initialize libsrtp */ if(srtp_init() != srtp_err_status_ok) { JANUS_LOG(LOG_FATAL, "Ops, error setting up libsrtp?\n"); return 5; } return 0; }
/* Transport implementation */ int janus_pfunix_init(janus_transport_callbacks *callback, const char *config_path) { if(g_atomic_int_get(&stopping)) { /* Still stopping from before */ return -1; } if(callback == NULL || config_path == NULL) { /* Invalid arguments */ return -1; } /* This is the callback we'll need to invoke to contact the gateway */ gateway = callback; /* Read configuration */ char filename[255]; g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_PFUNIX_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if(config != NULL) { /* Handle configuration */ janus_config_print(config); janus_config_item *item = janus_config_get_item_drilldown(config, "general", "json"); if(item && item->value) { /* Check how we need to format/serialize the JSON output */ if(!strcasecmp(item->value, "indented")) { /* Default: indented, we use three spaces for that */ json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; } else if(!strcasecmp(item->value, "plain")) { /* Not indented and no new lines, but still readable */ json_format = JSON_INDENT(0) | JSON_PRESERVE_ORDER; } else if(!strcasecmp(item->value, "compact")) { /* Compact, so no spaces between separators */ json_format = JSON_COMPACT | JSON_PRESERVE_ORDER; } else { JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', using default (indented)\n", item->value); json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; } } /* Check if we need to send events to handlers */ janus_config_item *events = janus_config_get_item_drilldown(config, "general", "events"); if(events != NULL && events->value != NULL) notify_events = janus_is_true(events->value); if(!notify_events && callback->events_is_enabled()) { JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_PFUNIX_NAME); } /* First of all, initialize the socketpair for writeable notifications */ if(socketpair(PF_LOCAL, SOCK_STREAM, 0, write_fd) < 0) { JANUS_LOG(LOG_FATAL, "Error creating socket pair for writeable events: %d, %s\n", errno, strerror(errno)); return -1; } /* Setup the Janus API Unix Sockets server(s) */ item = janus_config_get_item_drilldown(config, "general", "enabled"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "Unix Sockets server disabled (Janus API)\n"); } else { item = janus_config_get_item_drilldown(config, "general", "path"); char *pfname = (char *)(item && item->value ? item->value : NULL); item = janus_config_get_item_drilldown(config, "general", "type"); const char *type = item && item->value ? item->value : "SOCK_SEQPACKET"; dgram = FALSE; if(!strcasecmp(type, "SOCK_SEQPACKET")) { dgram = FALSE; } else if(!strcasecmp(type, "SOCK_DGRAM")) { dgram = TRUE; } else { JANUS_LOG(LOG_WARN, "Unknown type %s, assuming SOCK_SEQPACKET\n", type); type = "SOCK_SEQPACKET"; } if(pfname == NULL) { JANUS_LOG(LOG_WARN, "No path configured, skipping Unix Sockets server (Janus API)\n"); } else { JANUS_LOG(LOG_INFO, "Configuring %s Unix Sockets server (Janus API)\n", type); pfd = janus_pfunix_create_socket(pfname, dgram); } } /* Do the same for the Admin API, if enabled */ item = janus_config_get_item_drilldown(config, "admin", "admin_enabled"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "Unix Sockets server disabled (Admin API)\n"); } else { item = janus_config_get_item_drilldown(config, "admin", "admin_path"); char *pfname = (char *)(item && item->value ? item->value : NULL); item = janus_config_get_item_drilldown(config, "admin", "admin_type"); const char *type = item && item->value ? item->value : "SOCK_SEQPACKET"; if(!strcasecmp(type, "SOCK_SEQPACKET")) { admin_dgram = FALSE; } else if(!strcasecmp(type, "SOCK_DGRAM")) { admin_dgram = TRUE; } else { JANUS_LOG(LOG_WARN, "Unknown type %s, assuming SOCK_SEQPACKET\n", type); type = "SOCK_SEQPACKET"; } if(pfname == NULL) { JANUS_LOG(LOG_WARN, "No path configured, skipping Unix Sockets server (Admin API)\n"); } else { JANUS_LOG(LOG_INFO, "Configuring %s Unix Sockets server (Admin API)\n", type); admin_pfd = janus_pfunix_create_socket(pfname, admin_dgram); } } } janus_config_destroy(config); config = NULL; if(pfd < 0 && admin_pfd < 0) { JANUS_LOG(LOG_WARN, "No Unix Sockets server started, giving up...\n"); return -1; /* No point in keeping the plugin loaded */ } /* Create a couple of hashtables for all clients */ clients = g_hash_table_new(NULL, NULL); clients_by_fd = g_hash_table_new(NULL, NULL); clients_by_path = g_hash_table_new(g_str_hash, g_str_equal); janus_mutex_init(&clients_mutex); /* Start the Unix Sockets service thread */ GError *error = NULL; pfunix_thread = g_thread_try_new("pfunix thread", &janus_pfunix_thread, NULL, &error); if(!pfunix_thread) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Unix Sockets thread...\n", error->code, error->message ? error->message : "??"); return -1; } /* Done */ g_atomic_int_set(&initialized, 1); JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_PFUNIX_NAME); return 0; }
/* Transport implementation */ int janus_websockets_init(janus_transport_callbacks *callback, const char *config_path) { if(g_atomic_int_get(&stopping)) { /* Still stopping from before */ return -1; } if(callback == NULL || config_path == NULL) { /* Invalid arguments */ return -1; } /* This is the callback we'll need to invoke to contact the gateway */ gateway = callback; /* Read configuration */ char filename[255]; g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_WEBSOCKETS_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if(config != NULL) { janus_config_print(config); /* Handle configuration */ janus_config_item *item = janus_config_get_item_drilldown(config, "general", "ws_logging"); if(item && item->value) { ws_log_level = atoi(item->value); if(ws_log_level < 0) ws_log_level = 0; } JANUS_LOG(LOG_VERB, "libwebsockets logging: %d\n", ws_log_level); lws_set_log_level(ws_log_level, NULL); old_wss = NULL; janus_mutex_init(&old_wss_mutex); /* Any ACL for either the Janus or Admin API? */ item = janus_config_get_item_drilldown(config, "general", "ws_acl"); if(item && item->value) { gchar **list = g_strsplit(item->value, ",", -1); gchar *index = list[0]; if(index != NULL) { int i=0; while(index != NULL) { if(strlen(index) > 0) { JANUS_LOG(LOG_INFO, "Adding '%s' to the Janus API allowed list...\n", index); janus_websockets_allow_address(g_strdup(index), FALSE); } i++; index = list[i]; } } g_strfreev(list); list = NULL; } item = janus_config_get_item_drilldown(config, "admin", "admin_ws_acl"); if(item && item->value) { gchar **list = g_strsplit(item->value, ",", -1); gchar *index = list[0]; if(index != NULL) { int i=0; while(index != NULL) { if(strlen(index) > 0) { JANUS_LOG(LOG_INFO, "Adding '%s' to the Admin/monitor allowed list...\n", index); janus_websockets_allow_address(g_strdup(index), TRUE); } i++; index = list[i]; } } g_strfreev(list); list = NULL; } /* Setup the Janus API WebSockets server(s) */ item = janus_config_get_item_drilldown(config, "general", "ws"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "WebSockets server disabled\n"); } else { int wsport = 8188; item = janus_config_get_item_drilldown(config, "general", "ws_port"); if(item && item->value) wsport = atoi(item->value); /* Prepare context */ struct lws_context_creation_info info; memset(&info, 0, sizeof info); info.port = wsport; info.iface = NULL; info.protocols = wss_protocols; info.extensions = libwebsocket_get_internal_extensions(); info.ssl_cert_filepath = NULL; info.ssl_private_key_filepath = NULL; info.gid = -1; info.uid = -1; info.options = 0; /* Create the WebSocket context */ wss = libwebsocket_create_context(&info); if(wss == NULL) { JANUS_LOG(LOG_FATAL, "Error initializing libwebsock...\n"); } else { JANUS_LOG(LOG_INFO, "WebSockets server started (port %d)...\n", wsport); } } item = janus_config_get_item_drilldown(config, "general", "wss"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "Secure WebSockets server disabled\n"); } else { int wsport = 8989; item = janus_config_get_item_drilldown(config, "general", "wss_port"); if(item && item->value) wsport = atoi(item->value); item = janus_config_get_item_drilldown(config, "certificates", "cert_pem"); if(!item || !item->value) { JANUS_LOG(LOG_FATAL, "Missing certificate/key path\n"); } else { char *server_pem = (char *)item->value; char *server_key = (char *)item->value; item = janus_config_get_item_drilldown(config, "certificates", "cert_key"); if(item && item->value) server_key = (char *)item->value; JANUS_LOG(LOG_VERB, "Using certificates:\n\t%s\n\t%s\n", server_pem, server_key); /* Prepare secure context */ struct lws_context_creation_info info; memset(&info, 0, sizeof info); info.port = wsport; info.iface = NULL; info.protocols = swss_protocols; info.extensions = libwebsocket_get_internal_extensions(); info.ssl_cert_filepath = server_pem; info.ssl_private_key_filepath = server_key; info.gid = -1; info.uid = -1; info.options = 0; /* Create the secure WebSocket context */ swss = libwebsocket_create_context(&info); if(swss == NULL) { JANUS_LOG(LOG_FATAL, "Error initializing libwebsock...\n"); } else { JANUS_LOG(LOG_INFO, "Secure WebSockets server started (port %d)...\n", wsport); } } } /* Do the same for the Admin API, if enabled */ item = janus_config_get_item_drilldown(config, "admin", "admin_ws"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "Admin WebSockets server disabled\n"); } else { int wsport = 7188; item = janus_config_get_item_drilldown(config, "admin", "admin_ws_port"); if(item && item->value) wsport = atoi(item->value); /* Prepare context */ struct lws_context_creation_info info; memset(&info, 0, sizeof info); info.port = wsport; info.iface = NULL; info.protocols = admin_wss_protocols; info.extensions = libwebsocket_get_internal_extensions(); info.ssl_cert_filepath = NULL; info.ssl_private_key_filepath = NULL; info.gid = -1; info.uid = -1; info.options = 0; /* Create the WebSocket context */ admin_wss = libwebsocket_create_context(&info); if(admin_wss == NULL) { JANUS_LOG(LOG_FATAL, "Error initializing libwebsock...\n"); } else { JANUS_LOG(LOG_INFO, "Admin WebSockets server started (port %d)...\n", wsport); } } item = janus_config_get_item_drilldown(config, "admin", "admin_wss"); if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "Secure Admin WebSockets server disabled\n"); } else { int wsport = 7989; item = janus_config_get_item_drilldown(config, "admin", "admin_wss_port"); if(item && item->value) wsport = atoi(item->value); item = janus_config_get_item_drilldown(config, "certificates", "cert_pem"); if(!item || !item->value) { JANUS_LOG(LOG_FATAL, "Missing certificate/key path\n"); } else { char *server_pem = (char *)item->value; char *server_key = (char *)item->value; item = janus_config_get_item_drilldown(config, "certificates", "cert_key"); if(item && item->value) server_key = (char *)item->value; JANUS_LOG(LOG_VERB, "Using certificates:\n\t%s\n\t%s\n", server_pem, server_key); /* Prepare secure context */ struct lws_context_creation_info info; memset(&info, 0, sizeof info); info.port = wsport; info.iface = NULL; info.protocols = admin_swss_protocols; info.extensions = libwebsocket_get_internal_extensions(); info.ssl_cert_filepath = server_pem; info.ssl_private_key_filepath = server_key; info.gid = -1; info.uid = -1; info.options = 0; /* Create the secure WebSocket context */ admin_swss = libwebsocket_create_context(&info); if(admin_swss == NULL) { JANUS_LOG(LOG_FATAL, "Error initializing libwebsock...\n"); } else { JANUS_LOG(LOG_INFO, "Secure Admin WebSockets server started (port %d)...\n", wsport); } } } } janus_config_destroy(config); config = NULL; if(!wss && !swss && !admin_wss && !admin_swss) { JANUS_LOG(LOG_FATAL, "No WebSockets server started, giving up...\n"); return -1; /* No point in keeping the plugin loaded */ } wss_janus_api_enabled = wss || swss; wss_admin_api_enabled = admin_wss || admin_swss; GError *error = NULL; /* Start the WebSocket service threads */ if(wss != NULL) { wss_thread = g_thread_try_new("websockets thread", &janus_websockets_thread, wss, &error); if(!wss_thread) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the WebSockets thread...\n", error->code, error->message ? error->message : "??"); return -1; } } if(swss != NULL) { swss_thread = g_thread_try_new("secure websockets thread", &janus_websockets_thread, swss, &error); if(!swss_thread) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Secure WebSockets thread...\n", error->code, error->message ? error->message : "??"); return -1; } } if(admin_wss != NULL) { admin_wss_thread = g_thread_try_new("admin websockets thread", &janus_websockets_thread, admin_wss, &error); if(!admin_wss_thread) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Admin WebSockets thread...\n", error->code, error->message ? error->message : "??"); return -1; } } if(admin_swss != NULL) { admin_swss_thread = g_thread_try_new("secure admin websockets thread", &janus_websockets_thread, admin_swss, &error); if(!admin_swss_thread) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Secure Admin WebSockets thread...\n", error->code, error->message ? error->message : "??"); return -1; } } /* Done */ g_atomic_int_set(&initialized, 1); JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_WEBSOCKETS_NAME); return 0; }
void janus_turnrest_init(void) { /* Initialize libcurl, needed for contacting the TURN REST API backend */ curl_global_init(CURL_GLOBAL_ALL); janus_mutex_init(&api_mutex); }
/* Transport implementation */ int janus_rabbitmq_init(janus_transport_callbacks *callback, const char *config_path) { if(g_atomic_int_get(&stopping)) { /* Still stopping from before */ return -1; } if(callback == NULL || config_path == NULL) { /* Invalid arguments */ return -1; } /* This is the callback we'll need to invoke to contact the Janus core */ gateway = callback; /* Read configuration */ char filename[255]; g_snprintf(filename, 255, "%s/%s.jcfg", config_path, JANUS_RABBITMQ_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if(config == NULL) { JANUS_LOG(LOG_WARN, "Couldn't find .jcfg configuration file (%s), trying .cfg\n", JANUS_RABBITMQ_PACKAGE); g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_RABBITMQ_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); config = janus_config_parse(filename); } if(config != NULL) janus_config_print(config); janus_config_category *config_general = janus_config_get_create(config, NULL, janus_config_type_category, "general"); janus_config_category *config_admin = janus_config_get_create(config, NULL, janus_config_type_category, "admin"); janus_config_item *item = janus_config_get(config, config_general, janus_config_type_item, "json"); if(item && item->value) { /* Check how we need to format/serialize the JSON output */ if(!strcasecmp(item->value, "indented")) { /* Default: indented, we use three spaces for that */ json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; } else if(!strcasecmp(item->value, "plain")) { /* Not indented and no new lines, but still readable */ json_format = JSON_INDENT(0) | JSON_PRESERVE_ORDER; } else if(!strcasecmp(item->value, "compact")) { /* Compact, so no spaces between separators */ json_format = JSON_COMPACT | JSON_PRESERVE_ORDER; } else { JANUS_LOG(LOG_WARN, "Unsupported JSON format option '%s', using default (indented)\n", item->value); json_format = JSON_INDENT(3) | JSON_PRESERVE_ORDER; } } /* Check if we need to send events to handlers */ janus_config_item *events = janus_config_get(config, config_general, janus_config_type_item, "events"); if(events != NULL && events->value != NULL) notify_events = janus_is_true(events->value); if(!notify_events && callback->events_is_enabled()) { JANUS_LOG(LOG_WARN, "Notification of events to handlers disabled for %s\n", JANUS_RABBITMQ_NAME); } /* Handle configuration, starting from the server details */ item = janus_config_get(config, config_general, janus_config_type_item, "host"); if(item && item->value) rmqhost = g_strdup(item->value); else rmqhost = g_strdup("localhost"); int rmqport = AMQP_PROTOCOL_PORT; item = janus_config_get(config, config_general, janus_config_type_item, "port"); if(item && item->value) rmqport = atoi(item->value); /* Credentials and Virtual Host */ item = janus_config_get(config, config_general, janus_config_type_item, "vhost"); if(item && item->value) vhost = g_strdup(item->value); else vhost = g_strdup("/"); item = janus_config_get(config, config_general, janus_config_type_item, "username"); if(item && item->value) username = g_strdup(item->value); else username = g_strdup("guest"); item = janus_config_get(config, config_general, janus_config_type_item, "password"); if(item && item->value) password = g_strdup(item->value); else password = g_strdup("guest"); /* SSL config*/ gboolean ssl_enabled = FALSE; gboolean ssl_verify_peer = FALSE; gboolean ssl_verify_hostname = FALSE; item = janus_config_get(config, config_general, janus_config_type_item, "ssl_enabled"); if(item == NULL) { /* Try legacy property */ item = janus_config_get(config, config_general, janus_config_type_item, "ssl_enable"); if (item && item->value) { JANUS_LOG(LOG_WARN, "Found deprecated 'ssl_enable' property, please update it to 'ssl_enabled' instead\n"); } } if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_INFO, "RabbitMQ SSL support disabled\n"); } else { ssl_enabled = TRUE; item = janus_config_get(config, config_general, janus_config_type_item, "ssl_cacert"); if(item && item->value) ssl_cacert_file = g_strdup(item->value); item = janus_config_get(config, config_general, janus_config_type_item, "ssl_cert"); if(item && item->value) ssl_cert_file = g_strdup(item->value); item = janus_config_get(config, config_general, janus_config_type_item, "ssl_key"); if(item && item->value) ssl_key_file = g_strdup(item->value); item = janus_config_get(config, config_general, janus_config_type_item, "ssl_verify_peer"); if(item && item->value && janus_is_true(item->value)) ssl_verify_peer = TRUE; item = janus_config_get(config, config_general, janus_config_type_item, "ssl_verify_hostname"); if(item && item->value && janus_is_true(item->value)) ssl_verify_hostname = TRUE; } /* Now check if the Janus API must be supported */ item = janus_config_get(config, config_general, janus_config_type_item, "enabled"); if(item == NULL) { /* Try legacy property */ item = janus_config_get(config, config_general, janus_config_type_item, "enable"); if (item && item->value) { JANUS_LOG(LOG_WARN, "Found deprecated 'enable' property, please update it to 'enabled' instead\n"); } } if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "RabbitMQ support disabled (Janus API)\n"); } else { /* Parse configuration */ item = janus_config_get(config, config_general, janus_config_type_item, "to_janus"); if(!item || !item->value) { JANUS_LOG(LOG_FATAL, "Missing name of incoming queue for RabbitMQ integration...\n"); goto error; } to_janus = g_strdup(item->value); item = janus_config_get(config, config_general, janus_config_type_item, "from_janus"); if(!item || !item->value) { JANUS_LOG(LOG_FATAL, "Missing name of outgoing queue for RabbitMQ integration...\n"); goto error; } from_janus = g_strdup(item->value); item = janus_config_get(config, config_general, janus_config_type_item, "janus_exchange"); if(!item || !item->value) { JANUS_LOG(LOG_INFO, "Missing name of outgoing exchange for RabbitMQ integration, using default\n"); } else { janus_exchange = g_strdup(item->value); } if (janus_exchange == NULL) { JANUS_LOG(LOG_INFO, "RabbitMQ support for Janus API enabled, %s:%d (%s/%s)\n", rmqhost, rmqport, to_janus, from_janus); } else { JANUS_LOG(LOG_INFO, "RabbitMQ support for Janus API enabled, %s:%d (%s/%s) exch: (%s)\n", rmqhost, rmqport, to_janus, from_janus, janus_exchange); } rmq_janus_api_enabled = TRUE; } /* Do the same for the admin API */ item = janus_config_get(config, config_admin, janus_config_type_item, "admin_enabled"); if(item == NULL) { /* Try legacy property */ item = janus_config_get(config, config_general, janus_config_type_item, "admin_enable"); if (item && item->value) { JANUS_LOG(LOG_WARN, "Found deprecated 'admin_enable' property, please update it to 'admin_enabled' instead\n"); } } if(!item || !item->value || !janus_is_true(item->value)) { JANUS_LOG(LOG_WARN, "RabbitMQ support disabled (Admin API)\n"); } else { /* Parse configuration */ item = janus_config_get(config, config_admin, janus_config_type_item, "to_janus_admin"); if(!item || !item->value) { JANUS_LOG(LOG_FATAL, "Missing name of incoming queue for RabbitMQ integration...\n"); goto error; } to_janus_admin = g_strdup(item->value); item = janus_config_get(config, config_admin, janus_config_type_item, "from_janus_admin"); if(!item || !item->value) { JANUS_LOG(LOG_FATAL, "Missing name of outgoing queue for RabbitMQ integration...\n"); goto error; } from_janus_admin = g_strdup(item->value); JANUS_LOG(LOG_INFO, "RabbitMQ support for Admin API enabled, %s:%d (%s/%s)\n", rmqhost, rmqport, to_janus_admin, from_janus_admin); rmq_admin_api_enabled = TRUE; } if(!rmq_janus_api_enabled && !rmq_admin_api_enabled) { JANUS_LOG(LOG_WARN, "RabbitMQ support disabled for both Janus and Admin API, giving up\n"); goto error; } else { /* FIXME We currently support a single application, create a new janus_rabbitmq_client instance */ rmq_client = g_malloc0(sizeof(janus_rabbitmq_client)); /* Connect */ rmq_client->rmq_conn = amqp_new_connection(); amqp_socket_t *socket = NULL; int status; JANUS_LOG(LOG_VERB, "Creating RabbitMQ socket...\n"); if (ssl_enabled) { socket = amqp_ssl_socket_new(rmq_client->rmq_conn); if(socket == NULL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error creating socket...\n"); goto error; } if(ssl_verify_peer) { amqp_ssl_socket_set_verify_peer(socket, 1); } else { amqp_ssl_socket_set_verify_peer(socket, 0); } if(ssl_verify_hostname) { amqp_ssl_socket_set_verify_hostname(socket, 1); } else { amqp_ssl_socket_set_verify_hostname(socket, 0); } if(ssl_cacert_file) { status = amqp_ssl_socket_set_cacert(socket, ssl_cacert_file); if(status != AMQP_STATUS_OK) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error setting CA certificate... (%s)\n", amqp_error_string2(status)); goto error; } } if(ssl_cert_file && ssl_key_file) { status = amqp_ssl_socket_set_key(socket, ssl_cert_file, ssl_key_file); if(status != AMQP_STATUS_OK) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error setting key... (%s)\n", amqp_error_string2(status)); goto error; } } } else { socket = amqp_tcp_socket_new(rmq_client->rmq_conn); if(socket == NULL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error creating socket...\n"); goto error; } } JANUS_LOG(LOG_VERB, "Connecting to RabbitMQ server...\n"); status = amqp_socket_open(socket, rmqhost, rmqport); if(status != AMQP_STATUS_OK) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error opening socket... (%s)\n", amqp_error_string2(status)); goto error; } JANUS_LOG(LOG_VERB, "Logging in...\n"); amqp_rpc_reply_t result = amqp_login(rmq_client->rmq_conn, vhost, 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, username, password); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error logging in... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } rmq_client->rmq_channel = 1; JANUS_LOG(LOG_VERB, "Opening channel...\n"); amqp_channel_open(rmq_client->rmq_conn, rmq_client->rmq_channel); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error opening channel... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } rmq_client->janus_exchange = amqp_empty_bytes; if(janus_exchange != NULL) { JANUS_LOG(LOG_VERB, "Declaring exchange...\n"); rmq_client->janus_exchange = amqp_cstring_bytes(janus_exchange); amqp_exchange_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->janus_exchange, amqp_cstring_bytes(JANUS_RABBITMQ_EXCHANGE_TYPE), 0, 0, 0, 0, amqp_empty_table); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error diclaring exchange... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } } rmq_client->janus_api_enabled = FALSE; if(rmq_janus_api_enabled) { rmq_client->janus_api_enabled = TRUE; JANUS_LOG(LOG_VERB, "Declaring incoming queue... (%s)\n", to_janus); rmq_client->to_janus_queue = amqp_cstring_bytes(to_janus); amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->to_janus_queue, 0, 0, 0, 0, amqp_empty_table); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } JANUS_LOG(LOG_VERB, "Declaring outgoing queue... (%s)\n", from_janus); rmq_client->from_janus_queue = amqp_cstring_bytes(from_janus); amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->from_janus_queue, 0, 0, 0, 0, amqp_empty_table); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } amqp_basic_consume(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->to_janus_queue, amqp_empty_bytes, 0, 1, 0, amqp_empty_table); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error consuming... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } } rmq_client->admin_api_enabled = FALSE; if(rmq_admin_api_enabled) { rmq_client->admin_api_enabled = TRUE; JANUS_LOG(LOG_VERB, "Declaring incoming queue... (%s)\n", to_janus_admin); rmq_client->to_janus_admin_queue = amqp_cstring_bytes(to_janus_admin); amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->to_janus_admin_queue, 0, 0, 0, 0, amqp_empty_table); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } JANUS_LOG(LOG_VERB, "Declaring outgoing queue... (%s)\n", from_janus_admin); rmq_client->from_janus_admin_queue = amqp_cstring_bytes(from_janus_admin); amqp_queue_declare(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->from_janus_admin_queue, 0, 0, 0, 0, amqp_empty_table); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error declaring queue... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } amqp_basic_consume(rmq_client->rmq_conn, rmq_client->rmq_channel, rmq_client->to_janus_admin_queue, amqp_empty_bytes, 0, 1, 0, amqp_empty_table); result = amqp_get_rpc_reply(rmq_client->rmq_conn); if(result.reply_type != AMQP_RESPONSE_NORMAL) { JANUS_LOG(LOG_FATAL, "Can't connect to RabbitMQ server: error consuming... %s, %s\n", amqp_error_string2(result.library_error), amqp_method_name(result.reply.id)); goto error; } } rmq_client->messages = g_async_queue_new(); rmq_client->destroy = 0; /* Prepare the transport session (again, just one) */ rmq_session = janus_transport_session_create(rmq_client, NULL); /* Start the threads */ GError *error = NULL; rmq_client->in_thread = g_thread_try_new("rmq_in_thread", &janus_rmq_in_thread, rmq_client, &error); if(error != NULL) { /* Something went wrong... */ JANUS_LOG(LOG_FATAL, "Got error %d (%s) trying to launch the RabbitMQ incoming thread...\n", error->code, error->message ? error->message : "??"); janus_transport_session_destroy(rmq_session); g_free(rmq_client); janus_config_destroy(config); return -1; } rmq_client->out_thread = g_thread_try_new("rmq_out_thread", &janus_rmq_out_thread, rmq_client, &error); if(error != NULL) { /* Something went wrong... */ JANUS_LOG(LOG_FATAL, "Got error %d (%s) trying to launch the RabbitMQ outgoing thread...\n", error->code, error->message ? error->message : "??"); janus_transport_session_destroy(rmq_session); g_free(rmq_client); janus_config_destroy(config); return -1; } janus_mutex_init(&rmq_client->mutex); /* Done */ JANUS_LOG(LOG_INFO, "Setup of RabbitMQ integration completed\n"); /* Notify handlers about this new transport */ if(notify_events && gateway->events_is_enabled()) { json_t *info = json_object(); json_object_set_new(info, "event", json_string("connected")); gateway->notify_event(&janus_rabbitmq_transport, rmq_session, info); } } janus_config_destroy(config); config = NULL; /* Done */ g_atomic_int_set(&initialized, 1); JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_RABBITMQ_NAME); return 0; error: /* If we got here, something went wrong */ g_free(rmq_client); g_free(rmqhost); g_free(vhost); g_free(username); g_free(password); g_free(janus_exchange); g_free(to_janus); g_free(from_janus); g_free(to_janus_admin); g_free(from_janus_admin); g_free(ssl_cacert_file); g_free(ssl_cert_file); g_free(ssl_key_file); if(config) janus_config_destroy(config); return -1; }
janus_dtls_srtp *janus_dtls_srtp_create(void *ice_component, janus_dtls_role role) { janus_ice_component *component = (janus_ice_component *)ice_component; if(component == NULL) { JANUS_LOG(LOG_ERR, "No component, no DTLS...\n"); return NULL; } janus_ice_stream *stream = component->stream; if(!stream) { JANUS_LOG(LOG_ERR, "No stream, no DTLS...\n"); return NULL; } janus_ice_handle *handle = stream->handle; if(!handle || !handle->agent) { JANUS_LOG(LOG_ERR, "No handle/agent, no DTLS...\n"); return NULL; } janus_dtls_srtp *dtls = g_malloc0(sizeof(janus_dtls_srtp)); if(dtls == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); return NULL; } /* Create SSL context, at last */ dtls->srtp_valid = 0; dtls->ssl = SSL_new(janus_dtls_get_ssl_ctx()); if(!dtls->ssl) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] No component DTLS SSL session??\n", handle->handle_id); janus_dtls_srtp_destroy(dtls); return NULL; } SSL_set_ex_data(dtls->ssl, 0, dtls); SSL_set_info_callback(dtls->ssl, janus_dtls_callback); dtls->read_bio = BIO_new(BIO_s_mem()); if(!dtls->read_bio) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error creating read BIO!\n", handle->handle_id); janus_dtls_srtp_destroy(dtls); return NULL; } BIO_set_mem_eof_return(dtls->read_bio, -1); dtls->write_bio = BIO_new(BIO_s_mem()); if(!dtls->write_bio) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error creating write BIO!\n", handle->handle_id); janus_dtls_srtp_destroy(dtls); return NULL; } BIO_set_mem_eof_return(dtls->write_bio, -1); /* The write BIO needs our custom filter, or fragmentation won't work */ dtls->filter_bio = BIO_new(BIO_janus_dtls_filter()); if(!dtls->filter_bio) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error creating filter BIO!\n", handle->handle_id); janus_dtls_srtp_destroy(dtls); return NULL; } /* Chain filter and write BIOs */ BIO_push(dtls->filter_bio, dtls->write_bio); /* Set the filter as the BIO to use for outgoing data */ SSL_set_bio(dtls->ssl, dtls->read_bio, dtls->filter_bio); dtls->dtls_role = role; if(dtls->dtls_role == JANUS_DTLS_ROLE_CLIENT) { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Setting connect state (DTLS client)\n", handle->handle_id); SSL_set_connect_state(dtls->ssl); } else { JANUS_LOG(LOG_VERB, "[%"SCNu64"] Setting accept state (DTLS server)\n", handle->handle_id); SSL_set_accept_state(dtls->ssl); } /* https://code.google.com/p/chromium/issues/detail?id=406458 * Specify an ECDH group for ECDHE ciphers, otherwise they cannot be * negotiated when acting as the server. Use NIST's P-256 which is * commonly supported. */ EC_KEY* ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); if(ecdh == NULL) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error creating ECDH group!\n", handle->handle_id); janus_dtls_srtp_destroy(dtls); return NULL; } SSL_set_options(dtls->ssl, SSL_OP_SINGLE_ECDH_USE); SSL_set_tmp_ecdh(dtls->ssl, ecdh); EC_KEY_free(ecdh); dtls->ready = 0; #ifdef HAVE_SCTP dtls->sctp = NULL; #endif janus_mutex_init(&dtls->srtp_mutex); /* Done */ dtls->dtls_connected = 0; dtls->component = component; return dtls; }
/* DTLS-SRTP initialization */ gint janus_dtls_srtp_init(const char* server_pem, const char* server_key) { /* FIXME First of all make OpenSSL thread safe (see note above on issue #316) */ janus_dtls_locks = g_malloc0(sizeof(*janus_dtls_locks) * CRYPTO_num_locks()); int l=0; for(l = 0; l < CRYPTO_num_locks(); l++) { janus_mutex_init(&janus_dtls_locks[l]); } CRYPTO_THREADID_set_callback(janus_dtls_cb_openssl_threadid); CRYPTO_set_locking_callback(janus_dtls_cb_openssl_lock); /* Go on and create the DTLS context */ ssl_ctx = SSL_CTX_new(DTLSv1_method()); if(!ssl_ctx) { JANUS_LOG(LOG_FATAL, "Ops, error creating DTLS context?\n"); return -1; } SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, janus_dtls_verify_callback); SSL_CTX_set_tlsext_use_srtp(ssl_ctx, "SRTP_AES128_CM_SHA1_80"); /* FIXME Should we support something else as well? */ if (!server_pem && !server_key) { JANUS_LOG(LOG_WARN, "No cert/key specified, autogenerating some...\n"); if (janus_dtls_generate_keys(&ssl_cert, &ssl_key) != 0) { JANUS_LOG(LOG_FATAL, "Error generating DTLS key/certificate\n"); return -2; } } else if (!server_pem || !server_key) { JANUS_LOG(LOG_FATAL, "DTLS certificate and key must be specified\n"); return -2; } else if (janus_dtls_load_keys(server_pem, server_key, &ssl_cert, &ssl_key) != 0) { return -3; } if(!SSL_CTX_use_certificate(ssl_ctx, ssl_cert)) { JANUS_LOG(LOG_FATAL, "Certificate error (%s)\n", ERR_reason_error_string(ERR_get_error())); return -4; } if(!SSL_CTX_use_PrivateKey(ssl_ctx, ssl_key)) { JANUS_LOG(LOG_FATAL, "Certificate key error (%s)\n", ERR_reason_error_string(ERR_get_error())); return -5; } if(!SSL_CTX_check_private_key(ssl_ctx)) { JANUS_LOG(LOG_FATAL, "Certificate check error (%s)\n", ERR_reason_error_string(ERR_get_error())); return -6; } SSL_CTX_set_read_ahead(ssl_ctx,1); unsigned int size; unsigned char fingerprint[EVP_MAX_MD_SIZE]; if(X509_digest(ssl_cert, EVP_sha256(), (unsigned char *)fingerprint, &size) == 0) { JANUS_LOG(LOG_FATAL, "Error converting X509 structure (%s)\n", ERR_reason_error_string(ERR_get_error())); return -7; } char *lfp = (char *)&local_fingerprint; unsigned int i = 0; for(i = 0; i < size; i++) { g_snprintf(lfp, 4, "%.2X:", fingerprint[i]); lfp += 3; } *(lfp-1) = 0; JANUS_LOG(LOG_INFO, "Fingerprint of our certificate: %s\n", local_fingerprint); SSL_CTX_set_cipher_list(ssl_ctx, DTLS_CIPHERS); /* Initialize libsrtp */ if(srtp_init() != err_status_ok) { JANUS_LOG(LOG_FATAL, "Ops, error setting up libsrtp?\n"); return 5; } return 0; }
/* Plugin implementation */ int janus_source_init(janus_callbacks *callback, const char *config_path) { if (g_atomic_int_get(&stopping)) { /* Still stopping from before */ return -1; } if (callback == NULL || config_path == NULL) { /* Invalid arguments */ return -1; } /* Read configuration */ char filename[255]; g_snprintf(filename, 255, "%s/%s.cfg", config_path, JANUS_SOURCE_PACKAGE); JANUS_LOG(LOG_VERB, "Configuration file: %s\n", filename); janus_config *config = janus_config_parse(filename); if (config != NULL) janus_config_print(config); /* Parse configuration */ if (config != NULL) { GList *cl = janus_config_get_categories(config); while (cl != NULL) { janus_config_category *cat = (janus_config_category *)cl->data; if (cat->name == NULL) { cl = cl->next; continue; } JANUS_LOG(LOG_VERB, "Parsing category '%s'\n", cat->name); janus_source_parse_ports_range(janus_config_get_item(cat, "udp_port_range"), &udp_min_port, &udp_max_port); janus_source_parse_keepalive_interval(janus_config_get_item(cat, "keepalive_interval"), &keepalive_interval); janus_source_parse_status_service_url(janus_config_get_item(cat, "keepalive_service_url"), &keepalive_service_url); janus_source_parse_status_service_url(janus_config_get_item(cat,"status_service_url"),&status_service_url); janus_source_parse_video_codec_priority(janus_config_get_item(cat, "video_codec_priority")); janus_source_parse_rtsp_interface_ip(janus_config_get_item(cat, "interface"),&rtsp_interface_ip); cl = cl->next; } janus_config_destroy(config); config = NULL; } if (udp_min_port <= 0 || udp_max_port <= 0) { udp_min_port = 4000; udp_max_port = 5000; JANUS_LOG(LOG_WARN, "Using default port range: %d-%d\n", udp_min_port, udp_max_port); } sessions = g_hash_table_new(NULL, NULL); janus_mutex_init(&sessions_mutex); messages = g_async_queue_new_full((GDestroyNotify)janus_source_message_free); /* This is the callback we'll need to invoke to contact the gateway */ gateway = callback; g_atomic_int_set(&initialized, 1); GError *error = NULL; /* Start the sessions watchdog */ watchdog = g_thread_try_new("source watchdog", &janus_source_watchdog, NULL, &error); if (error != NULL) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the SourcePlugin watchdog thread...\n", error->code, error->message ? error->message : "??"); return -1; } gst_init(NULL, NULL); gst_debug_set_threshold_from_string(gst_debug_str, FALSE); curl_handle = curl_init(); socket_utils_init(udp_min_port, udp_max_port); /* Launch the thread that will handle incoming messages */ handler_thread = g_thread_try_new("janus source handler", janus_source_handler, NULL, &error); if (error != NULL) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Source handler thread...\n", error->code, error->message ? error->message : "??"); return -1; } /* Launch the thread that will handle rtsp clients */ handler_rtsp_thread = g_thread_try_new("rtsp server", janus_source_rtsp_server_thread, NULL, &error); if (error != NULL) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the Source rtsp server thread...\n", error->code, error->message ? error->message : "??"); return -1; } /* Set PID */ memset(&PID, 0, JANUS_PID_SIZE); if (0 > janus_set_pid()) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got an error while plugin id initialize."); return -1; } /*Start the keepalive thread */ keepalive = g_thread_try_new("source keepalive", &janus_source_keepalive, NULL, &error); if (error != NULL) { g_atomic_int_set(&initialized, 0); JANUS_LOG(LOG_ERR, "Got error %d (%s) trying to launch the SourcePlugin keepalive thread...\n", error->code, error->message ? error->message : "??"); return -1; } JANUS_LOG(LOG_INFO, "%s initialized!\n", JANUS_SOURCE_NAME); return 0; }
janus_recorder *janus_recorder_create(const char *dir, const char *codec, const char *filename) { janus_recorder_medium type = JANUS_RECORDER_AUDIO; if(codec == NULL) { JANUS_LOG(LOG_ERR, "Missing codec information\n"); return NULL; } if(!strcasecmp(codec, "vp8") || !strcasecmp(codec, "vp9") || !strcasecmp(codec, "h264")) { type = JANUS_RECORDER_VIDEO; } else if(!strcasecmp(codec, "opus") || !strcasecmp(codec, "g711") || !strcasecmp(codec, "pcmu") || !strcasecmp(codec, "pcma") || !strcasecmp(codec, "g722")) { type = JANUS_RECORDER_AUDIO; } else if(!strcasecmp(codec, "text")) { /* FIXME We only handle text on data channels, so that's the only thing we can save too */ type = JANUS_RECORDER_DATA; } else { /* We don't recognize the codec: while we might go on anyway, we'd rather fail instead */ JANUS_LOG(LOG_ERR, "Unsupported codec '%s'\n", codec); return NULL; } /* Create the recorder */ janus_recorder *rc = g_malloc0(sizeof(janus_recorder)); rc->dir = NULL; rc->filename = NULL; rc->file = NULL; rc->codec = g_strdup(codec); rc->created = janus_get_real_time(); const char *rec_dir = NULL; const char *rec_file = NULL; char *copy_for_parent = NULL; char *copy_for_base = NULL; /* Check dir and filename values */ if (filename != NULL) { /* Helper copies to avoid overwriting */ copy_for_parent = g_strdup(filename); copy_for_base = g_strdup(filename); /* Get filename parent folder */ const char *filename_parent = dirname(copy_for_parent); /* Get filename base file */ const char *filename_base = basename(copy_for_base); if (!dir) { /* If dir is NULL we have to create filename_parent and filename_base */ rec_dir = filename_parent; rec_file = filename_base; } else { /* If dir is valid we have to create dir and filename*/ rec_dir = dir; rec_file = filename; if (strcasecmp(filename_parent, ".") || strcasecmp(filename_base, filename)) { JANUS_LOG(LOG_WARN, "Unsupported combination of dir and filename %s %s\n", dir, filename); } } } if(rec_dir != NULL) { /* Check if this directory exists, and create it if needed */ struct stat s; int err = stat(rec_dir, &s); if(err == -1) { if(ENOENT == errno) { /* Directory does not exist, try creating it */ if(janus_mkdir(rec_dir, 0755) < 0) { JANUS_LOG(LOG_ERR, "mkdir error: %d\n", errno); return NULL; } } else { JANUS_LOG(LOG_ERR, "stat error: %d\n", errno); return NULL; } } else { if(S_ISDIR(s.st_mode)) { /* Directory exists */ JANUS_LOG(LOG_VERB, "Directory exists: %s\n", rec_dir); } else { /* File exists but it's not a directory? */ JANUS_LOG(LOG_ERR, "Not a directory? %s\n", rec_dir); return NULL; } } } char newname[1024]; memset(newname, 0, 1024); if(rec_file == NULL) { /* Choose a random username */ if(!rec_tempname) { /* Use .mjr as an extension right away */ g_snprintf(newname, 1024, "janus-recording-%"SCNu32".mjr", janus_random_uint32()); } else { /* Append the temporary extension to .mjr, we'll rename when closing */ g_snprintf(newname, 1024, "janus-recording-%"SCNu32".mjr.%s", janus_random_uint32(), rec_tempext); } } else { /* Just append the extension */ if(!rec_tempname) { /* Use .mjr as an extension right away */ g_snprintf(newname, 1024, "%s.mjr", rec_file); } else { /* Append the temporary extension to .mjr, we'll rename when closing */ g_snprintf(newname, 1024, "%s.mjr.%s", rec_file, rec_tempext); } } /* Try opening the file now */ if(rec_dir == NULL) { rc->file = fopen(newname, "wb"); } else { char path[1024]; memset(path, 0, 1024); g_snprintf(path, 1024, "%s/%s", rec_dir, newname); rc->file = fopen(path, "wb"); } if(rc->file == NULL) { JANUS_LOG(LOG_ERR, "fopen error: %d\n", errno); return NULL; } if(rec_dir) rc->dir = g_strdup(rec_dir); rc->filename = g_strdup(newname); rc->type = type; /* Write the first part of the header */ fwrite(header, sizeof(char), strlen(header), rc->file); g_atomic_int_set(&rc->writable, 1); /* We still need to also write the info header first */ g_atomic_int_set(&rc->header, 0); janus_mutex_init(&rc->mutex); /* Done */ g_atomic_int_set(&rc->destroyed, 0); janus_refcount_init(&rc->ref, janus_recorder_free); g_free(copy_for_parent); g_free(copy_for_base); return rc; }
janus_recorder *janus_recorder_create(const char *dir, const char *codec, const char *filename) { janus_recorder_medium type = JANUS_RECORDER_AUDIO; if(codec == NULL) { JANUS_LOG(LOG_ERR, "Missing codec information\n"); return NULL; } if(!strcasecmp(codec, "vp8") || !strcasecmp(codec, "vp9") || !strcasecmp(codec, "h264")) { type = JANUS_RECORDER_VIDEO; if(!strcasecmp(codec, "vp9")) { JANUS_LOG(LOG_WARN, "The post-processor currently doesn't support VP9: recording anyway for the future\n"); } } else if(!strcasecmp(codec, "opus") || !strcasecmp(codec, "g711") || !strcasecmp(codec, "pcmu") || !strcasecmp(codec, "pcma")) { type = JANUS_RECORDER_AUDIO; if(!strcasecmp(codec, "pcmu") || !strcasecmp(codec, "pcma")) codec = "g711"; } else if(!strcasecmp(codec, "text")) { /* FIXME We only handle text on data channels, so that's the only thing we can save too */ type = JANUS_RECORDER_DATA; } else { /* We don't recognize the codec: while we might go on anyway, we'd rather fail instead */ JANUS_LOG(LOG_ERR, "Unsupported codec '%s'\n", codec); return NULL; } /* Create the recorder */ janus_recorder *rc = g_malloc0(sizeof(janus_recorder)); if(rc == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); return NULL; } rc->dir = NULL; rc->filename = NULL; rc->file = NULL; rc->codec = g_strdup(codec); rc->created = janus_get_real_time(); if(dir != NULL) { /* Check if this directory exists, and create it if needed */ struct stat s; int err = stat(dir, &s); if(err == -1) { if(ENOENT == errno) { /* Directory does not exist, try creating it */ if(janus_mkdir(dir, 0755) < 0) { JANUS_LOG(LOG_ERR, "mkdir error: %d\n", errno); return NULL; } } else { JANUS_LOG(LOG_ERR, "stat error: %d\n", errno); return NULL; } } else { if(S_ISDIR(s.st_mode)) { /* Directory exists */ JANUS_LOG(LOG_VERB, "Directory exists: %s\n", dir); } else { /* File exists but it's not a directory? */ JANUS_LOG(LOG_ERR, "Not a directory? %s\n", dir); return NULL; } } } char newname[1024]; memset(newname, 0, 1024); if(filename == NULL) { /* Choose a random username */ g_snprintf(newname, 1024, "janus-recording-%"SCNu32".mjr", janus_random_uint32()); } else { /* Just append the extension */ g_snprintf(newname, 1024, "%s.mjr", filename); } /* Try opening the file now */ if(dir == NULL) { rc->file = fopen(newname, "wb"); } else { char path[1024]; memset(path, 0, 1024); g_snprintf(path, 1024, "%s/%s", dir, newname); rc->file = fopen(path, "wb"); } if(rc->file == NULL) { JANUS_LOG(LOG_ERR, "fopen error: %d\n", errno); return NULL; } if(dir) rc->dir = g_strdup(dir); rc->filename = g_strdup(newname); rc->type = type; /* Write the first part of the header */ fwrite(header, sizeof(char), strlen(header), rc->file); rc->writable = 1; /* We still need to also write the info header first */ rc->header = 0; janus_mutex_init(&rc->mutex); return rc; }
janus_sctp_association *janus_sctp_association_create(void *dtls, uint64_t handle_id, uint16_t udp_port) { if(dtls == NULL || udp_port == 0) return NULL; /* usrsctp provides UDP encapsulation of SCTP, but we need these messages to * be encapsulated in DTLS and actually sent/received by libnice, and not by * usrsctp itself... as such, we make use of the AF_CONN approach */ janus_sctp_association *sctp = (janus_sctp_association *)calloc(1, sizeof(janus_sctp_association)); if(sctp == NULL) { JANUS_LOG(LOG_FATAL, "Memory error!\n"); return NULL; } sctp->dtls = dtls; sctp->handle_id = handle_id; sctp->ready = FALSE; /* We wait for the association setup, for this */ sctp->local_port = 5000; /* FIXME We always use this one */ sctp->remote_port = udp_port; sctp->sock = NULL; struct socket *sock = NULL; unsigned int i = 0; struct sockaddr_conn sconn; /* Now go on with SCTP */ janus_sctp_channel *channel = NULL; for(i = 0; i < NUMBER_OF_CHANNELS; i++) { channel = &(sctp->channels[i]); channel->id = i; channel->state = DATA_CHANNEL_CLOSED; channel->pr_policy = SCTP_PR_SCTP_NONE; channel->pr_value = 0; channel->stream = 0; channel->unordered = 0; channel->flags = 0; } for(i = 0; i < NUMBER_OF_STREAMS; i++) { sctp->stream_channel[i] = NULL; sctp->stream_buffer[i] = 0; } sctp->stream_buffer_counter = 0; sctp->sock = NULL; janus_mutex_init(&sctp->mutex); usrsctp_register_address((void *)sctp); usrsctp_sysctl_set_sctp_ecn_enable(0); if((sock = usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, janus_sctp_incoming_data, NULL, 0, (void *)sctp)) == NULL) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error creating usrsctp socket...\n", handle_id); free(sctp); sctp = NULL; return NULL; } /* Set SO_LINGER */ struct linger linger_opt; linger_opt.l_onoff = 1; linger_opt.l_linger = 0; if(usrsctp_setsockopt(sock, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(linger_opt))) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] setsockopt error: SO_LINGER\n", handle_id); free(sctp); sctp = NULL; return NULL; } /* Allow resetting streams */ struct sctp_assoc_value av; av.assoc_id = SCTP_ALL_ASSOC; av.assoc_value = 1; if(usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_ENABLE_STREAM_RESET, &av, sizeof(struct sctp_assoc_value)) < 0) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] setsockopt error: SCTP_ENABLE_STREAM_RESET\n", handle_id); free(sctp); sctp = NULL; return NULL; } /* Disable Nagle */ uint32_t nodelay = 1; if(usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, sizeof(nodelay))) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] setsockopt error: SCTP_NODELAY\n", handle_id); free(sctp); sctp = NULL; return NULL; } /* Enable the events of interest */ struct sctp_event event; memset(&event, 0, sizeof(event)); event.se_assoc_id = SCTP_ALL_ASSOC; event.se_on = 1; for(i = 0; i < sizeof(event_types)/sizeof(uint16_t); i++) { event.se_type = event_types[i]; if(usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_EVENT, &event, sizeof(event)) < 0) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] setsockopt error: SCTP_EVENT\n", handle_id); free(sctp); sctp = NULL; return NULL; } } /* Configure our INIT message */ struct sctp_initmsg initmsg; memset(&initmsg, 0, sizeof(struct sctp_initmsg)); initmsg.sinit_num_ostreams = 16; /* What Firefox says in the INIT (Chrome says 1023) */ initmsg.sinit_max_instreams = 2048; /* What both Chrome and Firefox say in the INIT */ if(usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(struct sctp_initmsg)) < 0) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] setsockopt error: SCTP_INITMSG\n", handle_id); free(sctp); sctp = NULL; return NULL; } /* Bind our side of the communication, using AF_CONN as we're doing the actual delivery ourselves */ memset(&sconn, 0, sizeof(struct sockaddr_conn)); sconn.sconn_family = AF_CONN; sconn.sconn_port = htons(sctp->local_port); sconn.sconn_addr = (void *)sctp; if(usrsctp_bind(sock, (struct sockaddr *)&sconn, sizeof(struct sockaddr_conn)) < 0) { JANUS_LOG(LOG_ERR, "[%"SCNu64"] Error binding client on port %"SCNu16"\n", handle_id, sctp->local_port); free(sctp); sctp = NULL; return NULL; } #ifdef DEBUG_SCTP char debug_file[1024]; g_snprintf(debug_file, 1024, "%s/sctp-debug-%"SCNu64".txt", debug_folder, handle_id); sctp->debug_dump = fopen(debug_file, "wt"); #endif /* We're done for now, the setup is done elsewhere */ janus_mutex_lock(&sctp->mutex); sctp->sock = sock; sctp->in_messages = g_queue_new(); sctp->out_messages = g_queue_new(); GError *error = NULL; sctp->thread = g_thread_try_new("JanusSCTP", &janus_sctp_thread, sctp, &error); if(error != NULL) { /* Something went wrong... */ JANUS_LOG(LOG_ERR, "[%"SCNu64"] Got error %d (%s) trying to launch the SCTP thread...\n", handle_id, error->code, error->message ? error->message : "??"); g_queue_free(sctp->in_messages); sctp->in_messages = NULL; g_queue_free(sctp->out_messages); sctp->out_messages = NULL; g_free(sctp); sctp = NULL; return NULL; } janus_mutex_unlock(&sctp->mutex); return sctp; }